mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
Compare commits
78 Commits
release/v3
...
release/v3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e422b0758 | ||
|
|
47e7bdcb6c | ||
|
|
bea7fe4273 | ||
|
|
947f059dbf | ||
|
|
3b27bd1dbd | ||
|
|
bdadd44c4e | ||
|
|
a3476298d9 | ||
|
|
b6c2ece344 | ||
|
|
448b65af2c | ||
|
|
bcc2427f62 | ||
|
|
be985474d8 | ||
|
|
e082f7a31c | ||
|
|
23f52d0b58 | ||
|
|
475393748e | ||
|
|
24624f8802 | ||
|
|
f8fcbf2db1 | ||
|
|
7bcc756454 | ||
|
|
d1f35f3bde | ||
|
|
2175f0c041 | ||
|
|
0e56c5f54a | ||
|
|
0c94c397db | ||
|
|
f234cd6c1c | ||
|
|
77a44e26bc | ||
|
|
3a5b02b66a | ||
|
|
21f8dc4b3c | ||
|
|
95d1546965 | ||
|
|
bfecbac17e | ||
|
|
45150bdfe1 | ||
|
|
a7aec0e698 | ||
|
|
02f8bbccc7 | ||
|
|
3214dc6be6 | ||
|
|
92ee9f87dd | ||
|
|
92a3a421ee | ||
|
|
3f3ab87bf0 | ||
|
|
b596d52737 | ||
|
|
e4db78f7b0 | ||
|
|
eaa7009458 | ||
|
|
0c536cc362 | ||
|
|
fd820ebed7 | ||
|
|
b4bf19d49f | ||
|
|
4642bcaedf | ||
|
|
4b2aefac2c | ||
|
|
b90b6a307a | ||
|
|
7b3b03679f | ||
|
|
d8391b4b60 | ||
|
|
532208bf66 | ||
|
|
c73d025632 | ||
|
|
94b3566a7a | ||
|
|
08ee7b5f25 | ||
|
|
a4787f853e | ||
|
|
8d0f751b85 | ||
|
|
db9cc272e7 | ||
|
|
1a88b75d55 | ||
|
|
f86733874e | ||
|
|
9678a6404e | ||
|
|
f9ca142a46 | ||
|
|
0bc7288f83 | ||
|
|
921560ebbe | ||
|
|
98a8473f85 | ||
|
|
ba50f501a9 | ||
|
|
684f396410 | ||
|
|
b6e061ce78 | ||
|
|
d32bdfbd6c | ||
|
|
fb3f64e2ac | ||
|
|
baab21626f | ||
|
|
9543762a16 | ||
|
|
ad3d4b696a | ||
|
|
a1d8b31171 | ||
|
|
07341fcaae | ||
|
|
92c51104d4 | ||
|
|
8457d76a0b | ||
|
|
8b152fd4f7 | ||
|
|
69dc6d0249 | ||
|
|
f24a4c1ac6 | ||
|
|
3c512a7bba | ||
|
|
7a4905edbe | ||
|
|
4df424498b | ||
|
|
1f628e83fa |
52
.agents/skills/capture-release-evidences/SKILL.md
Normal file
52
.agents/skills/capture-release-evidences/SKILL.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: capture-release-evidences-cx
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** The `browser_subagent` tool referenced below is specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps remain the same regardless of the browser-automation surface in use.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
45
.agents/skills/deploy-vps-local/SKILL.md
Normal file
45
.agents/skills/deploy-vps-local/SKILL.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: deploy-vps-local-cx
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` only for independent commands. Do not parallelize dependent build, copy, install, restart, and verification steps.
|
||||
- Report each remote result explicitly before finishing.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
362
.agents/skills/generate-release/SKILL.md
Normal file
362
.agents/skills/generate-release/SKILL.md
Normal file
@@ -0,0 +1,362 @@
|
||||
---
|
||||
name: generate-release-cx
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- When the workflow says `notify_user` or `BlockedOnUser: true`, present the report/status in the final response and stop. Do not continue into the next phase until the user explicitly approves.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
### 🏆 Hall de Contribuidores
|
||||
|
||||
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
|
||||
@user1, @user2
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
> **🔴 HALL DE CONTRIBUIDORES RULE**: You MUST parse all the PR author mentions (e.g., `(thanks @username)`) from the new version's changelog items, deduplicate them, and append them as a "Hall de Contribuidores" section at the end of the new release block, exactly as shown above.
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Manually perform these documentation updates (there is no `/update-docs` workflow — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
```
|
||||
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 16. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 17. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 18. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` workflow anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
713
.agents/skills/implement-features/SKILL.md
Normal file
713
.agents/skills/implement-features/SKILL.md
Normal file
@@ -0,0 +1,713 @@
|
||||
---
|
||||
name: implement-features-cx
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- Approval gates are hard stops. Present the report/plan in the final response and do not move to implementation phases until the user explicitly approves.
|
||||
- Keep harvest/research bounded enough to produce the approval report quickly; do not start implementation while still in report phases.
|
||||
|
||||
**Output directory structure:**
|
||||
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
````
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
After implementing all approved features:
|
||||
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
51
.agents/skills/issue-triage/SKILL.md
Normal file
51
.agents/skills/issue-triage/SKILL.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: issue-triage-cx
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
173
.agents/skills/resolve-issues/SKILL.md
Normal file
173
.agents/skills/resolve-issues/SKILL.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: resolve-issues-cx
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- The initial report/plan is a hard stop. Do not edit code, close issues, or commit until the user explicitly approves the report.
|
||||
- Keep classification and bug analysis bounded enough to produce the user-facing report before deep implementation work.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
- **Bug** — Has `bug` label, or body contains error messages, stack traces, "doesn't work", "broken", "crash", "error"
|
||||
- **Feature Request** — Has `enhancement`/`feature` label, or body describes new functionality
|
||||
- **Question** — Has `question` label, or is asking "how to" something
|
||||
- **Other** — Anything else
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
After the user validates and gives the OK:
|
||||
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
126
.agents/skills/review-discussions/SKILL.md
Normal file
126
.agents/skills/review-discussions/SKILL.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: review-discussions-cx
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent`, modern runtimes should substitute with the `gh` CLI — `gh api graphql` for reading discussions and mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads and GitHub/browser fetches.
|
||||
- The summary report is a hard stop. Do not post discussion replies or create issues until the user explicitly approves.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
268
.agents/skills/review-prs/SKILL.md
Normal file
268
.agents/skills/review-prs/SKILL.md
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
name: review-prs-cx
|
||||
description: Analyze open Pull Requests from the project's GitHub repository, generate a critical report, and optionally implement approved changes
|
||||
---
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
The source Claude command uses `// turbo` and `// turbo-all` as execution hints. In Codex, treat them explicitly as follows:
|
||||
|
||||
- `// turbo`: batch independent local reads and small `gh`/`git` calls with `multi_tool_use.parallel`.
|
||||
- `// turbo-all`: fan out independent per-PR/per-issue calls in practical batches, usually up to 4 GitHub calls at a time.
|
||||
- Do not expand Step 4 into exhaustive CI-log debugging before Step 6. Fetch numbers, metadata, diffs/review comments, quick merge/conflict status, and only inspect extra logs when they directly affect the verdict.
|
||||
- Step 6 is a hard stop. In Codex, present the report in the final response and wait for the user before Step 7/8.
|
||||
- Do not checkout PR branches, edit files, post PR comments, close PRs, merge, cherry-pick, or run broad fix/test loops until the user explicitly approves the report.
|
||||
- If `gh pr diff` is too large, record the limit and use `gh pr view --json files` plus `git fetch refs/pull/...` with `git diff --stat` / `git diff --name-status`; only read targeted hunks needed for confirmed findings.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Read `package.json` to get the repository URL, or use the git remote origin URL
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with the appropriate file-read tool (`Read` in Claude Code; equivalent in your agent runtime).
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
- **Risks & Issues** — Categorized as CRITICAL / IMPORTANT / MINOR
|
||||
- **Scoring Table** — Rate across: Feature Relevance, Code Quality, Security, Robustness, Tests
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report in the final response and stop. Mark this as a blocking checkpoint awaiting explicit user approval before continuing.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7) or use cherry-picking into the release branch.
|
||||
2. If you managed to fix their branch, merge it into the release branch using the GitHub CLI:
|
||||
`gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"`
|
||||
3. If you had to use cherry-picking because you couldn't push to their branch, DO NOT close the PR. GitHub will sometimes auto-detect the cherry-picked commits and mark it as Merged. If it doesn't, leave it open. The repository owner will handle it. NEVER run `gh pr close`.
|
||||
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch or cherry-picked).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
347
.agents/skills/version-bump/SKILL.md
Normal file
347
.agents/skills/version-bump/SKILL.md
Normal file
@@ -0,0 +1,347 @@
|
||||
---
|
||||
name: version-bump-cx
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
## Codex Execution Notes
|
||||
|
||||
- Treat `// turbo` / `// turbo-all` as instructions to use `multi_tool_use.parallel` for independent reads, checks, and GitHub calls.
|
||||
- Any user-approval phase is a hard stop: present the report/status in the final response and wait before committing, pushing, tagging, publishing, or deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — there is no `/update-i18n` workflow shipped in this repo.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
51
.agents/workflows/capture-release-evidences-ag.md
Normal file
51
.agents/workflows/capture-release-evidences-ag.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** The `browser_subagent` tool referenced below is specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps remain the same regardless of the browser-automation surface in use.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
39
.agents/workflows/deploy-vps-local-ag.md
Normal file
39
.agents/workflows/deploy-vps-local-ag.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
356
.agents/workflows/generate-release-ag.md
Normal file
356
.agents/workflows/generate-release-ag.md
Normal file
@@ -0,0 +1,356 @@
|
||||
---
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
### 🏆 Hall de Contribuidores
|
||||
|
||||
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
|
||||
@user1, @user2
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
> **🔴 HALL DE CONTRIBUIDORES RULE**: You MUST parse all the PR author mentions (e.g., `(thanks @username)`) from the new version's changelog items, deduplicate them, and append them as a "Hall de Contribuidores" section at the end of the new release block, exactly as shown above.
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Manually perform these documentation updates (there is no `/update-docs` workflow — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
```
|
||||
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 16. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 17. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 18. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` workflow anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
706
.agents/workflows/implement-features-ag.md
Normal file
706
.agents/workflows/implement-features-ag.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
**Output directory structure:**
|
||||
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
````
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
After implementing all approved features:
|
||||
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
50
.agents/workflows/issue-triage-ag.md
Normal file
50
.agents/workflows/issue-triage-ag.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
166
.agents/workflows/resolve-issues-ag.md
Normal file
166
.agents/workflows/resolve-issues-ag.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
- **Bug** — Has `bug` label, or body contains error messages, stack traces, "doesn't work", "broken", "crash", "error"
|
||||
- **Feature Request** — Has `enhancement`/`feature` label, or body describes new functionality
|
||||
- **Question** — Has `question` label, or is asking "how to" something
|
||||
- **Other** — Anything else
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
After the user validates and gives the OK:
|
||||
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
120
.agents/workflows/review-discussions-ag.md
Normal file
120
.agents/workflows/review-discussions-ag.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent`, in modern Claude Code substitute with the `gh` CLI via Bash — `gh api graphql` for reading discussions and mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
256
.agents/workflows/review-prs-ag.md
Normal file
256
.agents/workflows/review-prs-ag.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: Analyze open Pull Requests from the project's GitHub repository, generate a critical report, and optionally implement approved changes
|
||||
---
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Read `package.json` to get the repository URL, or use the git remote origin URL
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with the appropriate file-read tool (`Read` in Claude Code; equivalent in your agent runtime).
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
- **Risks & Issues** — Categorized as CRITICAL / IMPORTANT / MINOR
|
||||
- **Scoring Table** — Rate across: Feature Relevance, Code Quality, Security, Robustness, Tests
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report in the final response and stop. Mark this as a blocking checkpoint awaiting explicit user approval.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7) or use cherry-picking into the release branch.
|
||||
2. If you managed to fix their branch, merge it into the release branch using the GitHub CLI:
|
||||
`gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"`
|
||||
3. If you had to use cherry-picking because you couldn't push to their branch, DO NOT close the PR. GitHub will sometimes auto-detect the cherry-picked commits and mark it as Merged. If it doesn't, leave it open. The repository owner will handle it. NEVER run `gh pr close`.
|
||||
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch or cherry-picked).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
341
.agents/workflows/version-bump-ag.md
Normal file
341
.agents/workflows/version-bump-ag.md
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — there is no `/update-i18n` workflow shipped in this repo.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
51
.claude/commands/capture-release-evidences-cc.md
Normal file
51
.claude/commands/capture-release-evidences-cc.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
description: Automatically run a browser-automation agent to visually validate all new UI features from the current release and capture evidence WebP recordings of the changes.
|
||||
---
|
||||
|
||||
# Capture Release Evidences Workflow
|
||||
|
||||
Use this workflow to automatically drive a browser-automation agent to explore the newly deployed or locally running application and record evidence of the UI changes introduced in the latest release.
|
||||
|
||||
> **Tool mapping note (v3.8):** This workflow references a `browser_subagent` tool that was specific to an earlier agent runtime. In Claude Code, substitute with the available browser MCP tools (e.g. `mcp__claude-in-chrome__*`) for navigation/screenshots, plus the `Write` tool for saving artifacts. The high-level steps below remain the same regardless of which browser-automation surface is used.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OmniRoute must be actively running and accessible (e.g. locally at `http://localhost:20128` or on the Local VPS at `http://192.168.0.15:20128`).
|
||||
- The user must provide the target URL to be tested, or default to `http://192.168.0.15:20128`.
|
||||
|
||||
## Workflow Steps
|
||||
|
||||
### 1. Identify Target Features
|
||||
|
||||
Review the `CHANGELOG.md` for the latest version to map out the new UI elements. For example:
|
||||
|
||||
- **CLI Tools Settings**
|
||||
- **New Provider/Model Listings (e.g., Gemini 3.1, Qoder PAT)**
|
||||
- **New Feature Modals**
|
||||
|
||||
### 2. Run the Browser Subagent
|
||||
|
||||
For each identified feature, invoke the `browser_subagent` using the `default_api:browser_subagent` tool.
|
||||
**Important Task Guidelines for the Subagent:**
|
||||
|
||||
- `TaskName`: Give it a clear name like "Validate CLIProxyAPI Tool Tab".
|
||||
- `TaskSummary`: "Navigate to the CLI Tools tab and verify the new Integration settings."
|
||||
- `Task`: Provide unambiguous instructions for the subagent, such as: "Navigate to http://192.168.0.15:20128/dashboard. Click on the 'Settings' or 'CLI Tools' nav link. Scroll down to find the CLIProxyAPI integration card. Hover over it to trigger UI state. Verify the components render correctly and exit."
|
||||
- `RecordingName`: Ensure it describes the feature (e.g. `v3_4_5_cli_proxy_api`). This is required and strictly automatically saved as a WebP artifacts video by the system.
|
||||
|
||||
_(Note: The `browser_subagent` automatically creates a WebP recording named by the `RecordingName` parameter. No additional tools for screenshots are needed.)_
|
||||
|
||||
### 3. Generate Report Artifact
|
||||
|
||||
After the `browser_subagent` finishes its sessions, generate a final Markdown artifact (using `Write` and `IsArtifact=true`) to present the recordings inline to the user using the `` syntax.
|
||||
|
||||
### Example Invocation
|
||||
|
||||
\```json
|
||||
{
|
||||
"TaskName": "Validating Qoder PAT Configuration UI",
|
||||
"TaskSummary": "Validates the Qoder provider configuration modal",
|
||||
"Task": "Go to http://192.168.0.15:20128/dashboard. Click on the 'Providers' tab. Find 'Qoder' in the list. Click 'Add Token' or 'Configure'. Type 'test_token' and submit. Return when done.",
|
||||
"RecordingName": "qoder_pat_ui_validation"
|
||||
}
|
||||
\```
|
||||
39
.claude/commands/deploy-vps-akamai-cc.md
Normal file
39
.claude/commands/deploy-vps-akamai-cc.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
---
|
||||
|
||||
# Deploy to Akamai VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Akamai VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Akamai VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
```
|
||||
49
.claude/commands/deploy-vps-both-cc.md
Normal file
49
.claude/commands/deploy-vps-both-cc.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to BOTH the Akamai VPS and the Local VPS
|
||||
---
|
||||
|
||||
# Deploy to VPS (Both) Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPSs using `npm pack + scp` + PM2.
|
||||
|
||||
**Akamai VPS:** `69.164.221.35`
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to both VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'AKAMAI HTTP %{http_code}\n' http://69.164.221.35:20128/
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
39
.claude/commands/deploy-vps-local-cc.md
Normal file
39
.claude/commands/deploy-vps-local-cc.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
description: Deploy the latest OmniRoute code to the Local VPS (192.168.0.15)
|
||||
---
|
||||
|
||||
# Deploy to Local VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the Local VPS using `npm pack + scp` + PM2.
|
||||
|
||||
**Local VPS:** `192.168.0.15`
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Build + pack locally
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
```
|
||||
|
||||
### 2. Copy to Local VPS and install
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w 'LOCAL HTTP %{http_code}\n' http://192.168.0.15:20128/
|
||||
```
|
||||
362
.claude/commands/generate-release-cc.md
Normal file
362
.claude/commands/generate-release-cc.md
Normal file
@@ -0,0 +1,362 @@
|
||||
---
|
||||
description: Create a new release, bump version up to the .10 patch threshold, update changelog, and manage Pull Requests
|
||||
---
|
||||
|
||||
# Generate Release Workflow
|
||||
|
||||
Bump version, finalize CHANGELOG, commit, open a **PR to main** and wait for user confirmation before tagging, publishing, and deploying.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.8.10` → `3.9.0`.
|
||||
|
||||
> **🔴 SINGLE BRANCH RULE**: The `release/vX.Y.Z` branch is the **ONLY** development branch for the entire release cycle. ALL work — bug fixes, feature implementations, PR integrations, issue resolutions — MUST be committed directly on this branch. Never create separate `fix/`, `feat/`, or topic branches. When running `/resolve-issues`, `/implement-features`, or `/review-prs`, always work on the current release branch.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Two-Phase Flow
|
||||
|
||||
```
|
||||
Phase 1 (automated): bump → docs → i18n → commit → push → open PR
|
||||
↕ 🛑 STOP: Notify user, wait for PR confirmation
|
||||
Phase 2 (post-merge): tag → publish → GitHub release → Docker → deploy
|
||||
```
|
||||
|
||||
**NEVER push directly to main or create tags before the user confirms the PR.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Security Verification (MANDATORY)
|
||||
|
||||
Before creating the release, you must ensure the codebase and supply chain are secure and free of known vulnerabilities.
|
||||
|
||||
1. **Run Local Dependencies Audit:**
|
||||
|
||||
```bash
|
||||
npm audit
|
||||
```
|
||||
|
||||
_Fix any `high` or `critical` vulnerabilities identified._
|
||||
|
||||
2. **Check GitHub CodeQL & Dependabot Alerts:**
|
||||
Navigate to the repository's **Security** tab on GitHub, or use the project's `vulnerability-scanner` skill to analyze active alerts. Ensure all static analysis findings (e.g., prototype pollution, insecure randomness, ReDoS, shell injections) are addressed and logically committed on a target branch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Pre-Merge
|
||||
|
||||
### 1. Create release branch
|
||||
|
||||
```bash
|
||||
git checkout -b release/v3.x.y
|
||||
```
|
||||
|
||||
### 2. Determine and sync version
|
||||
|
||||
Check current version in `package.json`:
|
||||
|
||||
```bash
|
||||
grep '"version"' package.json
|
||||
```
|
||||
|
||||
> **🔴 BRANCH-VERSION PARITY RULE**: The logical version in `package.json` MUST exactly match the release branch name. For example, if you are on `release/v3.7.0`, the version in `package.json` MUST be `3.7.0`.
|
||||
>
|
||||
> - If this is the FIRST time generating a release for a new minor/major branch (e.g., bumping from 3.6.9 to 3.7.0), you MUST ensure the version is bumped to match the new branch logic.
|
||||
> - If you are just bumping a patch on the current branch (e.g., 3.6.9 to 3.6.10), use:
|
||||
> `npm version patch --no-git-tag-version`
|
||||
|
||||
> **⚠️ ATOMIC COMMIT RULE — Version bump MUST happen before committing feature files.**
|
||||
>
|
||||
> **CORRECT order:**
|
||||
>
|
||||
> 1. `npm version patch --no-git-tag-version` ← bump first
|
||||
> 2. implement features / fix bugs
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **OR if features are already staged:**
|
||||
>
|
||||
> 1. implement features (do NOT commit yet)
|
||||
> 2. `npm version patch --no-git-tag-version` ← bump before committing
|
||||
> 3. `git add -A && git commit -m "chore(release): v3.x.y — all changes in ONE commit"`
|
||||
>
|
||||
> **NEVER do this (creates version mismatch in git history):**
|
||||
>
|
||||
> - ~~commit features → then bump version → commit package.json separately~~
|
||||
>
|
||||
> This ensures that `git show v3.x.y` always contains both code changes and the version bump together.
|
||||
> The GitHub release tag will point to a commit that includes ALL changes for that version.
|
||||
|
||||
### 3. Regenerate lock file (REQUIRED after version bump)
|
||||
|
||||
**Mandatory** — skipping causes `@swc/helpers` lock mismatch and CI failures:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 4. Finalize CHANGELOG.md
|
||||
|
||||
> **🔴 NO MIXUPS RULE**: Ensure you do NOT mix the backlog of the previous version with the new one. The new version section must ONLY contain the features and fixes for the current release.
|
||||
|
||||
Replace the `[Unreleased]` header with the new version and date.
|
||||
Keep an empty `## [Unreleased]` section above it, separated by a horizontal rule (`---`).
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [3.7.0] — 2026-04-19
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- ...
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- ...
|
||||
|
||||
---
|
||||
|
||||
## [3.6.9] — 2026-04-19
|
||||
```
|
||||
|
||||
### 5. Update openapi.yaml version ⚠️ MANDATORY
|
||||
|
||||
> **CI will fail** if `docs/reference/openapi.yaml` version ≠ `package.json` version (`check:docs-sync` enforces this).
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ openapi.yaml → $VERSION"
|
||||
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
# Re-run install to assert the workspace lockfile is updated
|
||||
npm install
|
||||
```
|
||||
|
||||
### 6. Update README.md and i18n docs
|
||||
|
||||
Manually perform these documentation updates (there is no `/update-docs` slash command — it was deprecated in v3.8):
|
||||
|
||||
- Update feature table rows and "What's new in vX.Y.Z" section in `README.md`
|
||||
- Sync feature changes to all 40 language `docs/i18n/*/README.md` files (use the same row edits across each translated README)
|
||||
- Update the relevant `docs/<AREA>.md` if architecture or counts changed (e.g. `docs/frameworks/MCP-SERVER.md` when MCP tools change)
|
||||
- Re-run `npm run check:docs-sync` and `npm run check:docs-all` to catch drift
|
||||
|
||||
### 7. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
All tests must pass before creating the PR.
|
||||
|
||||
### 8. Stage, commit, and push
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore(release): v3.x.y — summary of changes"
|
||||
git push origin release/v3.x.y
|
||||
```
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
### 9. Open PR to main
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extract the exact changelog entry for this version from the root CHANGELOG.md
|
||||
awk "/^## \\[$VERSION\\]/{flag=1; print; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md > /tmp/changelog_body.txt
|
||||
|
||||
# Append test status and next steps
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### Tests" >> /tmp/changelog_body.txt
|
||||
echo "- All tests pass" >> /tmp/changelog_body.txt
|
||||
echo "" >> /tmp/changelog_body.txt
|
||||
echo "### ⚠️ After merging: run Phase 2 steps to tag, publish, and deploy." >> /tmp/changelog_body.txt
|
||||
|
||||
gh pr create \
|
||||
--repo diegosouzapw/OmniRoute \
|
||||
--base main \
|
||||
--head release/v$VERSION \
|
||||
--title "Release v$VERSION" \
|
||||
--body-file /tmp/changelog_body.txt
|
||||
```
|
||||
|
||||
### 10. 🛑 STOP — Notify User & Await PR Confirmation
|
||||
|
||||
**This is a mandatory stop point.** Present the report in the final response and stop. Do not continue to the next phase until the user explicitly approves.
|
||||
|
||||
Inform the user:
|
||||
|
||||
- PR URL
|
||||
- Summary of changes
|
||||
- Test results
|
||||
- List of files changed
|
||||
|
||||
**DO NOT proceed to Phase 2 until the user confirms the PR looks good and merges it.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Post-Merge Validation (Local VPS)
|
||||
|
||||
> Run these steps only AFTER the user has merged the PR into `main` and all CI jobs have passed.
|
||||
|
||||
### 11. Deploy to Local VPS for Final Validation (MANDATORY)
|
||||
|
||||
Before cutting the official git tag and publishing to the world, deploy the `main` branch to the Local VPS for a final homologation test.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Build and pack locally
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute && rm -f omniroute-*.tgz && rm -rf .next/cache app/.next/cache && npm run build:cli && rm -rf app/logs app/coverage app/.git app/.app-build-backup* && npm pack --ignore-scripts
|
||||
|
||||
# Deploy to LOCAL VPS (192.168.0.15)
|
||||
scp omniroute-*.tgz root@192.168.0.15:/tmp/
|
||||
ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Local done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "LOCAL: HTTP %{http_code}\n" http://192.168.0.15:20128/
|
||||
```
|
||||
|
||||
### 12. 🛑 STOP — Notify User & Await Final OK
|
||||
|
||||
**This is a mandatory stop point.**
|
||||
Inform the user that the `main` branch is now running on the Local VPS.
|
||||
Wait for the user to manually test and give the **OK**.
|
||||
**DO NOT proceed to Phase 3 until the user confirms the local deploy is stable.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Official Launch
|
||||
|
||||
> Run these steps only AFTER the user gives the final OK from the Phase 2 local validation.
|
||||
|
||||
### 13. Create Git Tag and GitHub Release (MANDATORY)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Extracts the changelog section for this version
|
||||
NOTES=$(awk "/^## \\[$VERSION\\]/{flag=1; next} /^---/{if(flag) {flag=0; exit}} flag" CHANGELOG.md | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
if [ -z "$NOTES" ]; then NOTES="OmniRoute v$VERSION Release"; fi
|
||||
|
||||
git tag -a "v$VERSION" -m "Release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
gh release create "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES" --target main || gh release edit "v$VERSION" --repo diegosouzapw/OmniRoute --title "v$VERSION" --notes "$NOTES"
|
||||
```
|
||||
|
||||
### 14. 🐳 Trigger Docker Hub build (MANDATORY — keep npm and Docker in sync)
|
||||
|
||||
> **CRITICAL**: Docker Hub and npm MUST always publish the same version.
|
||||
> The Docker image is built automatically via GitHub Actions when a new tag is pushed.
|
||||
> After pushing the tag in step 13, **verify the workflow runs**:
|
||||
|
||||
```bash
|
||||
# Verify the Docker workflow triggered
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 3
|
||||
|
||||
# Wait for the Docker build to complete (usually 5–10 min)
|
||||
gh run watch --repo diegosouzapw/OmniRoute
|
||||
```
|
||||
|
||||
### 15. Publish to NPM (Optional/Automated)
|
||||
|
||||
Normally handled by CI, but if manual publish is required:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
### 16. Deploy to AKAMAI VPS (Production)
|
||||
|
||||
Now that the release is officially cut, deploy it to the Akamai VPS.
|
||||
|
||||
```bash
|
||||
# Deploy to AKAMAI VPS (69.164.221.35)
|
||||
scp omniroute-*.tgz root@69.164.221.35:/tmp/
|
||||
ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && cd /usr/lib/node_modules/omniroute/app && npm rebuild better-sqlite3 && pm2 delete omniroute 2>/dev/null; pm2 start /root/.omniroute/ecosystem.config.cjs --update-env && pm2 save && echo '✅ Akamai done'"
|
||||
|
||||
# Verify
|
||||
curl -s -o /dev/null -w "AKAMAI: HTTP %{http_code}\n" http://69.164.221.35:20128/
|
||||
```
|
||||
|
||||
## Phase 4: Release Monitoring & Artifact Validation
|
||||
|
||||
> After triggering the official release, actively monitor the CI pipelines until all artifacts are successfully generated. If any pipeline fails, stop and apply the necessary corrections before continuing.
|
||||
|
||||
### 18. Monitor CI Pipelines
|
||||
|
||||
Wait for and verify the successful completion of the following automated jobs:
|
||||
|
||||
1. **Docker Hub Publish**
|
||||
2. **Electron Build**
|
||||
3. **NPM Registry Publish** (Check with `npm info omniroute version`)
|
||||
|
||||
```bash
|
||||
# Monitor Docker Hub workflow
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow docker-publish.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Monitor Electron build
|
||||
gh run list --repo diegosouzapw/OmniRoute --workflow electron-release.yml --limit 1
|
||||
gh run watch <RUN_ID>
|
||||
|
||||
# Verify NPM version
|
||||
npm info omniroute version
|
||||
```
|
||||
|
||||
### 19. Handle Failures (If Any)
|
||||
|
||||
If a workflow fails:
|
||||
|
||||
- Use `gh run view <RUN_ID> --log-failed` to identify the error.
|
||||
- Apply the fix on the `main` branch.
|
||||
- If necessary, re-trigger the workflow using `gh workflow run <workflow_name.yml> --repo diegosouzapw/OmniRoute --ref v3.x.y`
|
||||
|
||||
### 20. Preserve release branch
|
||||
|
||||
```bash
|
||||
# Branch is kept for historical purposes. Do not delete.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure CHANGELOG, README and `docs/*` are current BEFORE this workflow — run `npm run check:docs-all` and `/version-bump` first (there is no `/update-docs` slash command anymore)
|
||||
- The `prepublishOnly` script runs `npm run build:cli` automatically during `npm publish`
|
||||
- After npm publish, verify with `npm info omniroute version`
|
||||
- Lock file sync errors are caused by skipping `npm install` after version bump
|
||||
- Use `gh auth switch -u diegosouzapw` if git push fails with wrong account
|
||||
|
||||
## Known CI Pitfalls
|
||||
|
||||
| CI failure | Cause | Fix |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| `[docs-sync] FAIL - OpenAPI version differs from package.json` | Skipped step 5 — `docs/reference/openapi.yaml` version not updated | Run step 5 (`sed -i ...`) and commit |
|
||||
| `[docs-sync] FAIL - CHANGELOG.md first section must be "## [Unreleased]"` | `## [Unreleased]` missing or not at top of CHANGELOG | Add `## [Unreleased]\n\n---\n` before the first versioned `## [x.y.z]` |
|
||||
| Electron Linux `.deb` build fails (`FpmTarget` error) | `fpm` Ruby gem not installed on `ubuntu-latest` runner | Already fixed in `electron-release.yml` (`gem install fpm` step) |
|
||||
| Docker Hub `502 error writing layer blob` | Transient Docker Hub network error during ARM64 push | Re-run the Docker publish workflow; no code change needed |
|
||||
706
.claude/commands/implement-features-cc.md
Normal file
706
.claude/commands/implement-features-cc.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
description: Analyze open feature request issues, implement viable ones on dedicated branches, and respond to authors
|
||||
---
|
||||
|
||||
# /implement-features — Feature Request Harvest, Research & Implementation Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
A **5-phase** workflow that systematically harvests feature requests from GitHub issues, creates structured idea files, researches solutions across the internet and Git repositories, presents a consolidated report for user approval, then generates detailed implementation plans and executes them.
|
||||
|
||||
**Output directory structure:**
|
||||
|
||||
```
|
||||
_ideia/
|
||||
├── viable/ # Features approved for implementation
|
||||
│ ├── need_details/ # ❓ Good idea but waiting for author clarification (issues stay OPEN)
|
||||
│ │ └── 1015-warp-terminal-mitm.md
|
||||
│ ├── 1046-native-playground.md # ✅ Ready — researched and planned
|
||||
│ └── 1046-native-playground.requirements.md
|
||||
├── defer/ # ⏭️ Good ideas deferred for future cycles (issues CLOSED)
|
||||
│ └── 1041-smart-auto-combos.md
|
||||
└── notfit/ # ❌ Out of scope / already exists (issues CLOSED)
|
||||
└── 945-telegram-integration.md
|
||||
|
||||
_tasks/features-vX.Y.Z/ # Implementation plans (per-release)
|
||||
└── 1046-native-playground.plan.md
|
||||
```
|
||||
|
||||
> **LIFECYCLE RULE:** `viable/` files are **DELETED** once the feature is implemented — they are not moved. Only unimplemented features live in `viable/` (or `viable/need_details/`). Files in `defer/` and `notfit/` remain as permanent reference.
|
||||
|
||||
> **BRANCH RULE**: All implementation work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `feat/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Harvest: Collect & Catalog Feature Ideas
|
||||
|
||||
### 1.1 Identify the Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract owner/repo.
|
||||
|
||||
### 1.2 Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 1.3 Fetch ALL Open Feature Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below.
|
||||
|
||||
**Step 1 — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
```bash
|
||||
# Fetch issues with feature/enhancement labels
|
||||
gh issue list --repo <owner>/<repo> --state open -l "enhancement" --limit 500 --json number --jq '.[].number'
|
||||
|
||||
# Also check for [Feature] in title (common pattern when no labels are set)
|
||||
gh issue list --repo <owner>/<repo> --state open --limit 500 --json number,title --jq '.[] | select(.title | test("\\[Feature\\]|\\[feature\\]|feature request"; "i")) | .number'
|
||||
```
|
||||
|
||||
- Merge both lists, deduplicate. Count and confirm the total.
|
||||
|
||||
**Step 2 — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
```bash
|
||||
gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author,assignees
|
||||
```
|
||||
|
||||
- Read the **entire body** — including description, use cases, screenshots, mockups, and any embedded images.
|
||||
- Read **ALL comments** — community discussion, agreements, restrictions, owner responses, and linked PRs.
|
||||
- **Images**: If the body or comments contain image URLs (`` or `https://...png/jpg/gif`), note them — they may contain UI mockups, wireframes, or architecture diagrams that are essential to understanding the request.
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 1.4 Create Idea Files (initially in `_ideia/` root)
|
||||
|
||||
For each feature request, create a structured idea file in `<project_root>/_ideia/`:
|
||||
|
||||
**Filename convention**: `<NUMBER>-<kebab-case-short-title>.md`
|
||||
Example: `1046-native-playground.md`, `1041-smart-auto-combos.md`
|
||||
|
||||
#### 1.4a — If the idea file does NOT exist yet, create it:
|
||||
|
||||
```markdown
|
||||
# Feature: <Title from Issue>
|
||||
|
||||
> GitHub Issue: #<NUMBER> — opened by @<author> on <date>
|
||||
> Status: 📋 Cataloged | Priority: TBD
|
||||
|
||||
## 📝 Original Request
|
||||
|
||||
<Paste the FULL issue body here, preserving all formatting, images, and code blocks>
|
||||
|
||||
## 💬 Community Discussion
|
||||
|
||||
<Summarize ALL comments chronologically, noting who said what and any decisions or objections raised>
|
||||
|
||||
### Participants
|
||||
|
||||
- @<author> — Original requester
|
||||
- @<commenter1> — <brief role/opinion>
|
||||
- ...
|
||||
|
||||
### Key Points
|
||||
|
||||
- <bullet list of the most important discussion points>
|
||||
- <agreements reached>
|
||||
- <objections raised>
|
||||
|
||||
## 🎯 Refined Feature Description
|
||||
|
||||
<YOUR interpretation and enrichment of the feature request. Expand on what was asked, fill in logical gaps, provide concrete examples of how it would work. This section should be MORE detailed and clearer than the original request.>
|
||||
|
||||
### What it solves
|
||||
|
||||
- <problem 1>
|
||||
- <problem 2>
|
||||
|
||||
### How it should work (high level)
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. ...
|
||||
|
||||
### Affected areas
|
||||
|
||||
- <list of codebase areas, modules, files likely affected>
|
||||
|
||||
## 📎 Attachments & References
|
||||
|
||||
- <any image URLs, mockup links, or external references from the issue>
|
||||
|
||||
## 🔗 Related Ideas
|
||||
|
||||
- <links to related \_ideia/ files if any overlap found>
|
||||
```
|
||||
|
||||
#### 1.4b — If the idea file ALREADY exists, update it:
|
||||
|
||||
- Append new comments from the issue to the **Community Discussion** section.
|
||||
- Update the **Refined Feature Description** if new information changes the understanding.
|
||||
- Add any new **Related Ideas** cross-references found.
|
||||
- **Do NOT overwrite** existing content — append and enrich it.
|
||||
|
||||
### 1.5 Cross-Reference & Deduplication
|
||||
|
||||
After processing all issues:
|
||||
|
||||
- Scan all `_ideia/*.md` files for overlapping features.
|
||||
- If two features are substantially the same, add `🔗 Related Ideas` cross-references to both.
|
||||
- If one is a strict subset of another, note it in the smaller file: `> ℹ️ This feature is a subset of #<OTHER_NUMBER>. Consider implementing together.`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Research: Find Solutions & Build Requirements
|
||||
|
||||
For each cataloged idea that is **viable** (aligns with the project's goals):
|
||||
|
||||
### 2.1 Viability Pre-Check
|
||||
|
||||
Before investing in research, quickly assess:
|
||||
|
||||
- [ ] Does this feature align with the project's goals and architecture?
|
||||
- [ ] Is it technically feasible with the current codebase?
|
||||
- [ ] Does it duplicate existing functionality?
|
||||
- [ ] Would it introduce breaking changes or security risks?
|
||||
- [ ] Is there enough detail to understand what's needed?
|
||||
|
||||
**Verdict options:**
|
||||
|
||||
| Verdict | When | Action |
|
||||
| --------------------- | ------------------------------------- | --------------------------- |
|
||||
| ✅ **VIABLE** | Good idea, enough context | Proceed to Research |
|
||||
| ❓ **NEEDS DETAIL** | Good idea, insufficient spec | Skip research, ask author |
|
||||
| ⏭️ **DEFER** | Good idea, too complex for this cycle | Catalog only, skip research |
|
||||
| ❌ **NOT FIT** | Doesn't fit the project | Explain why |
|
||||
| 🔁 **ALREADY EXISTS** | Feature already implemented | Point to existing feature |
|
||||
|
||||
### 2.2 Internet Research (for VIABLE features)
|
||||
|
||||
For each viable feature, perform systematic research:
|
||||
|
||||
**Step 1 — Web search for similar implementations:**
|
||||
|
||||
```
|
||||
WebSearch("how to implement <feature description> in <tech stack>")
|
||||
WebSearch("<feature keyword> implementation nextjs typescript 2025 2026")
|
||||
WebSearch("<feature keyword> open source library npm")
|
||||
```
|
||||
|
||||
**Step 2 — Find reference Git repositories:**
|
||||
|
||||
```
|
||||
WebSearch("site:github.com <feature keyword> <tech stack> stars:>100")
|
||||
WebSearch("github <feature keyword> implementation recently updated 2026")
|
||||
```
|
||||
|
||||
- Find **up to 10 relevant repositories**, sorted by most recently updated.
|
||||
- For each repository:
|
||||
- Note the repo URL, star count, last commit date
|
||||
- Read its README and relevant source files via `WebFetch`
|
||||
- Extract the architectural approach, patterns used, and key code snippets
|
||||
|
||||
**Step 3 — Read API docs and standards:**
|
||||
|
||||
If the feature involves an external API, protocol, or standard:
|
||||
|
||||
- Find and read the official documentation
|
||||
- Note version requirements, authentication patterns, rate limits
|
||||
|
||||
### 2.3 Create Requirements File
|
||||
|
||||
For each researched feature, create a requirements file alongside its idea file:
|
||||
|
||||
**Filename**: `<NUMBER>-<kebab-case-short-title>.requirements.md`
|
||||
|
||||
```markdown
|
||||
# Requirements: <Feature Title>
|
||||
|
||||
> Feature Idea: [#<NUMBER>](./<NUMBER>-<kebab-case-short-title>.md)
|
||||
> Research Date: <YYYY-MM-DD>
|
||||
> Verdict: ✅ VIABLE
|
||||
|
||||
## 🔍 Research Summary
|
||||
|
||||
<Brief summary of what was found during research>
|
||||
|
||||
## 📚 Reference Implementations
|
||||
|
||||
| # | Repository | Stars | Last Updated | Approach | Relevance |
|
||||
| --- | ---------------- | ----- | ------------ | -------- | ------------ |
|
||||
| 1 | [repo/name](url) | ⭐ N | YYYY-MM-DD | <brief> | High/Med/Low |
|
||||
| 2 | ... | | | | |
|
||||
|
||||
### Key Patterns Found
|
||||
|
||||
- <pattern 1 with code snippet or link>
|
||||
- <pattern 2>
|
||||
|
||||
## 📐 Proposed Solution Architecture
|
||||
|
||||
### Approach
|
||||
|
||||
<Describe the chosen approach based on research findings>
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ------------- |
|
||||
| `path/to/new/file.ts` | <description> |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------- | -------------- |
|
||||
| `path/to/existing/file.ts` | <what changes> |
|
||||
|
||||
### Database Changes
|
||||
|
||||
- <migrations needed, if any>
|
||||
|
||||
### API Changes
|
||||
|
||||
- <new/modified endpoints, if any>
|
||||
|
||||
### UI Changes
|
||||
|
||||
- <new/modified pages/components, if any>
|
||||
|
||||
## ⚙️ Implementation Effort
|
||||
|
||||
- **Estimated complexity**: Low / Medium / High / Very High
|
||||
- **Estimated files changed**: ~N
|
||||
- **Dependencies needed**: <new npm packages, if any>
|
||||
- **Breaking changes**: Yes/No — <details>
|
||||
- **i18n impact**: <number of new translation keys>
|
||||
- **Test coverage needed**: <brief description>
|
||||
|
||||
## ⚠️ Open Questions
|
||||
|
||||
- <question 1>
|
||||
- <question 2>
|
||||
|
||||
## 🔗 External References
|
||||
|
||||
- <documentation URLs>
|
||||
- <API references>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — Organize & Respond: Sort Files and Post GitHub Comments
|
||||
|
||||
### 2.5.1 Create Directory Structure
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_ideia/viable
|
||||
mkdir -p <project_root>/_ideia/viable/need_details
|
||||
mkdir -p <project_root>/_ideia/defer
|
||||
mkdir -p <project_root>/_ideia/notfit
|
||||
```
|
||||
|
||||
### 2.5.2 Move Idea Files to Category Subdirectories
|
||||
|
||||
After classification, move EVERY idea file to its correct subdirectory:
|
||||
|
||||
```bash
|
||||
# ✅ VIABLE — move idea + requirements files
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/
|
||||
mv _ideia/<NUMBER>-*.requirements.md _ideia/viable/
|
||||
|
||||
# ❓ NEEDS DETAIL — viable but waiting for author response
|
||||
mv _ideia/<NUMBER>-*.md _ideia/viable/need_details/
|
||||
|
||||
# ⏭️ DEFER — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/defer/
|
||||
|
||||
# ❌ NOT FIT & 🔁 ALREADY EXISTS — move idea files only
|
||||
mv _ideia/<NUMBER>-*.md _ideia/notfit/
|
||||
```
|
||||
|
||||
No files should remain in `_ideia/` root after this step (except subdirectories).
|
||||
|
||||
### 2.5.3 Post GitHub Comments by Category
|
||||
|
||||
**Each category has a specific comment template and action:**
|
||||
|
||||
---
|
||||
|
||||
#### For 🔁 ALREADY EXISTS — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
The feature already exists in the system. Explain WHERE it is and HOW to use it.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
Great news — this functionality **already exists** in OmniRoute:
|
||||
|
||||
**📍 Where to find it:** <exact dashboard path or settings location>
|
||||
|
||||
**🔧 How to use it:**
|
||||
|
||||
1. <step 1>
|
||||
2. <step 2>
|
||||
3. <step 3>
|
||||
|
||||
If you have any trouble finding or using it, feel free to ask in a Discussion. We're always happy to help!
|
||||
|
||||
Closing this as the feature is already available. 🎉
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ⏭️ DEFER — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, explain the idea was cataloged, and that we'll study it before implementing.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for this thoughtful feature request! 🙏
|
||||
|
||||
We really appreciate the detailed proposal. We've **cataloged your idea** and it's now part of our improvement backlog.
|
||||
|
||||
Due to the **significant architectural impact** of this feature, we'll need to conduct thorough use-case studies and architectural analysis before we start development. This ensures we build it right and don't introduce regressions.
|
||||
|
||||
**What happens next:**
|
||||
|
||||
- Your idea is saved in our internal feature backlog
|
||||
- We'll conduct architecture studies when this area is prioritized
|
||||
- We'll notify you here when development begins
|
||||
|
||||
Thank you for contributing to OmniRoute's roadmap! Your input helps shape the product. 🚀
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❌ NOT FIT — Comment + CLOSE issue
|
||||
|
||||
// turbo
|
||||
|
||||
Politely explain why the feature doesn't fit the project scope.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the suggestion! 🙏
|
||||
|
||||
After careful analysis, we've determined that this feature **falls outside OmniRoute's core scope** as a proxy/router.
|
||||
|
||||
**Reason:** <explain why — e.g., "Telegram integration belongs in the application/orchestrator layer that consumes OmniRoute's API, not inside the router itself.">
|
||||
|
||||
**Alternative:** <suggest an alternative approach if possible>
|
||||
|
||||
We appreciate you thinking of ways to improve OmniRoute! If you'd like to discuss this further, feel free to open a Discussion. 🙏
|
||||
```
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ❓ NEEDS DETAIL — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Ask for the specific missing details needed.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the feature request — it's an interesting idea and we'd love to explore it further. 🙏
|
||||
|
||||
To move forward, we need a few more details:
|
||||
|
||||
1. <specific question 1>
|
||||
2. <specific question 2>
|
||||
3. <specific question 3>
|
||||
|
||||
If you know of any **open-source projects or repositories** that implement something similar, please share links — it would help us design the best solution.
|
||||
|
||||
Looking forward to your response! 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### For ✅ VIABLE — Comment (keep OPEN)
|
||||
|
||||
// turbo
|
||||
|
||||
Thank the user, confirm we've cataloged their idea, and explain it may be implemented in future versions.
|
||||
|
||||
```markdown
|
||||
Hi @<author>! Thanks for the great feature suggestion! 🙏
|
||||
|
||||
We've analyzed your request and it aligns well with OmniRoute's roadmap. We've **cataloged this feature** and it's in our implementation backlog.
|
||||
|
||||
**Status:** 📋 Cataloged for future implementation
|
||||
|
||||
This feature may be included in upcoming releases. We'll **respond to this issue and tag you** as soon as implementation begins so you can test it.
|
||||
|
||||
Thank you for helping improve OmniRoute! 🚀
|
||||
```
|
||||
|
||||
**⚠️ Do NOT close viable issues — they remain OPEN for tracking.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Report: Present Findings to User
|
||||
|
||||
### 3.1 🛑 MANDATORY STOP — Present Consolidated Report
|
||||
|
||||
After completing Phase 1, Phase 2, and Phase 2.5, **STOP and present the following report** in the chat. Do NOT proceed to implementation.
|
||||
|
||||
Present a structured report containing:
|
||||
|
||||
#### 3.1a — Feature Summary Table
|
||||
|
||||
| # | Issue | Title | Verdict | Location | Action |
|
||||
| --- | ----- | ----- | --------------- | ----------------------------- | ----------------------------- |
|
||||
| 1 | #N | Title | ✅ VIABLE | `_ideia/viable/` | Issue OPEN, comment posted |
|
||||
| 2 | #N | Title | ⏭️ DEFER | `_ideia/defer/` | Issue CLOSED with explanation |
|
||||
| 3 | #N | Title | ❌ NOT FIT | `_ideia/notfit/` | Issue CLOSED with explanation |
|
||||
| 4 | #N | Title | 🔁 EXISTS | `_ideia/notfit/` | Issue CLOSED with guidance |
|
||||
| 5 | #N | Title | ❓ NEEDS DETAIL | `_ideia/viable/need_details/` | Issue OPEN, questions posted |
|
||||
|
||||
#### 3.1b — Viable Features Detail
|
||||
|
||||
For each VIABLE feature, provide a brief paragraph:
|
||||
|
||||
- What was found during research
|
||||
- The proposed approach
|
||||
- Key risks or unknowns
|
||||
- Which reference repositories were most useful
|
||||
|
||||
#### 3.1c — Issues Requiring Author Feedback
|
||||
|
||||
For features marked ❓ NEEDS DETAIL, list:
|
||||
|
||||
- What specific information is missing
|
||||
- What examples or repository references would help
|
||||
|
||||
#### 3.1d — Ask for User Confirmation
|
||||
|
||||
End the report with:
|
||||
|
||||
> **Ready to proceed with implementation?**
|
||||
>
|
||||
> - Reply **"sim"** or **"yes"** to generate full implementation plans for all VIABLE features.
|
||||
> - Reply with specific issue numbers to select only certain features.
|
||||
> - Reply **"não"** or **"no"** to stop here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Plan: Generate Implementation Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 3.**
|
||||
|
||||
### 4.1 Create Task Directory
|
||||
|
||||
```bash
|
||||
mkdir -p <project_root>/_tasks/features-vX.Y.Z/
|
||||
```
|
||||
|
||||
### 4.2 Generate One Implementation Plan Per Feature
|
||||
|
||||
For each VIABLE feature approved by the user, create:
|
||||
|
||||
**Filename**: `_tasks/features-vX.Y.Z/<NUMBER>-<kebab-case-title>.plan.md`
|
||||
|
||||
```markdown
|
||||
# Implementation Plan: <Feature Title>
|
||||
|
||||
> Issue: #<NUMBER>
|
||||
> Idea: [\_ideia/viable/<NUMBER>-title.md](../../_ideia/viable/<NUMBER>-title.md)
|
||||
> Requirements: [\_ideia/viable/<NUMBER>-title.requirements.md](../../_ideia/viable/<NUMBER>-title.requirements.md)
|
||||
> Branch: `release/vX.Y.Z`
|
||||
|
||||
## Overview
|
||||
|
||||
<Brief description of what will be built>
|
||||
|
||||
## Pre-Implementation Checklist
|
||||
|
||||
- [ ] Read all related source files listed below
|
||||
- [ ] Confirm no conflicts with in-flight PRs
|
||||
- [ ] Verify database migration numbering
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: <Title>
|
||||
|
||||
**Files:**
|
||||
|
||||
- `path/to/file.ts` — <what to change>
|
||||
|
||||
**Details:**
|
||||
<Detailed description of the change, including code patterns to follow, function signatures, etc.>
|
||||
|
||||
### Step 2: <Title>
|
||||
|
||||
...
|
||||
|
||||
### Step N: Tests
|
||||
|
||||
**New test files:**
|
||||
|
||||
- `tests/unit/<test-file>.test.mjs` — <what to test>
|
||||
|
||||
**Test cases:**
|
||||
|
||||
- [ ] <test case 1>
|
||||
- [ ] <test case 2>
|
||||
|
||||
### Step N+1: i18n
|
||||
|
||||
**Translation keys to add:**
|
||||
|
||||
- `<namespace>.<key>` — "<English value>"
|
||||
|
||||
### Step N+2: Documentation
|
||||
|
||||
- [ ] Update CHANGELOG.md
|
||||
- [ ] Update relevant docs/ files
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run `npm run build` — must pass
|
||||
2. Run `npm test` — all tests must pass
|
||||
3. Run `npm run lint` — no new errors
|
||||
4. <Manual verification steps>
|
||||
|
||||
## Commit Plan
|
||||
```
|
||||
|
||||
feat: <description> (#<NUMBER>)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### 4.3 Present Plans for Final Approval
|
||||
|
||||
Present a summary of all generated plans:
|
||||
|
||||
> **Implementation plans generated:**
|
||||
>
|
||||
> | # | Feature | Plan File | Steps | Effort |
|
||||
> | --- | ------- | ---------------------------------------- | ------- | ------ |
|
||||
> | 1 | <title> | `_tasks/features-vX.Y.Z/N-title.plan.md` | N steps | Medium |
|
||||
>
|
||||
> Reply **"sim"** or **"yes"** to begin implementation of all features.
|
||||
> Reply with specific issue numbers to implement only certain ones.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Execute: Implement the Plans (after user says "yes")
|
||||
|
||||
> **⚠️ Do NOT enter this phase without explicit user approval from Phase 4.**
|
||||
|
||||
### 5.1 Implement Each Feature
|
||||
|
||||
For each approved plan, execute it step by step:
|
||||
|
||||
1. **Follow the plan** — implement exactly as specified in the `.plan.md` file
|
||||
2. **Build** — Run `npm run build` after each feature to verify compilation
|
||||
3. **Test** — Run `npm test` to ensure no regressions
|
||||
4. **Commit** — Commit with: `feat: <description> (#<NUMBER>)`
|
||||
5. **Update the plan** — Mark completed steps with `[x]` in the plan file
|
||||
6. **Continue** — Move to the next feature (do NOT switch branches)
|
||||
|
||||
### 5.2 Respond to Authors (Update Viable Issues)
|
||||
|
||||
For each implemented feature, **close the issue with a final comment**:
|
||||
|
||||
````markdown
|
||||
✅ **Implemented in `release/vX.Y.Z`!**
|
||||
|
||||
Hi @<author>! Great news — your feature request has been implemented! 🎉
|
||||
|
||||
**What was done:**
|
||||
|
||||
- <bullet list of what was built>
|
||||
|
||||
**How to try it:**
|
||||
|
||||
```bash
|
||||
git fetch origin && git checkout release/vX.Y.Z
|
||||
npm install && npm run dev
|
||||
```
|
||||
````
|
||||
|
||||
This will be included in the upcoming **vX.Y.Z** release. Feel free to reopen if you spot any issues! 🚀
|
||||
|
||||
````
|
||||
|
||||
```bash
|
||||
gh issue close <NUMBER> --repo <owner>/<repo> --comment "<comment above>"
|
||||
````
|
||||
|
||||
Then **DELETE the idea file** — it has served its purpose:
|
||||
|
||||
```bash
|
||||
# ✅ Implemented files are DELETED (not moved)
|
||||
rm _ideia/viable/<NUMBER>-<title>.md
|
||||
rm _ideia/viable/<NUMBER>-<title>.requirements.md # if exists
|
||||
```
|
||||
|
||||
> **Why delete?** `viable/` only holds features that still NEED to be done. Once implemented, the commit history and CHANGELOG are the source of truth. Keeping the file would be confusing.
|
||||
|
||||
### 5.3 Finalize & Push
|
||||
|
||||
After implementing all approved features:
|
||||
|
||||
1. **Update CHANGELOG.md** on the release branch with all new feature entries
|
||||
2. Push the release branch: `git push origin release/vX.Y.Z`
|
||||
3. Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
|
||||
### 5.4 Final Summary Report
|
||||
|
||||
Present a final summary report to the user:
|
||||
|
||||
| Issue | Title | Verdict | Action | Commit |
|
||||
| ----- | ----- | --------------- | -------------------------------------------------- | --------- |
|
||||
| #N | Title | ✅ Implemented | Issue closed, idea file deleted | `abc1234` |
|
||||
| #N | Title | ⏭️ Deferred | Issue closed + saved in `_ideia/defer/` | — |
|
||||
| #N | Title | ❌ Not Fit | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | 🔁 Exists | Issue closed + saved in `_ideia/notfit/` | — |
|
||||
| #N | Title | ❓ Needs Detail | Issue OPEN, moved to `_ideia/viable/need_details/` | — |
|
||||
|
||||
Include:
|
||||
|
||||
- Total features harvested
|
||||
- Total ideas cataloged (`viable/need_details/` + `defer/` + `notfit/`)
|
||||
- Total features implemented (idea files deleted, issues closed)
|
||||
- Total features deferred
|
||||
- Total issues closed
|
||||
- Total issues left open (needs detail only — viable are closed after implementation)
|
||||
- Test results (pass/fail count)
|
||||
50
.claude/commands/issue-triage-cc.md
Normal file
50
.claude/commands/issue-triage-cc.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: How to respond to GitHub issues with insufficient information
|
||||
---
|
||||
|
||||
# Issue Triage Workflow
|
||||
|
||||
Respond to GitHub issues that need more information before they can be investigated.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify issues needing triage
|
||||
|
||||
```bash
|
||||
gh issue list --state open --limit 20
|
||||
```
|
||||
|
||||
### 2. Evaluate each issue
|
||||
|
||||
Check if the issue has:
|
||||
|
||||
- Clear reproduction steps
|
||||
- Environment details (OS, Node.js version, OmniRoute version)
|
||||
- Error logs/screenshots
|
||||
- Expected vs actual behavior
|
||||
|
||||
### 3. Respond with triage template
|
||||
|
||||
For issues missing information:
|
||||
|
||||
```markdown
|
||||
Thank you for reporting this issue! To help us investigate, please provide:
|
||||
|
||||
1. **OmniRoute version**: (`omniroute --version`)
|
||||
2. **Node.js version**: (`node --version`)
|
||||
3. **Operating system**: (e.g., Ubuntu 24.04, macOS 15, Windows 11)
|
||||
4. **Installation method**: (npm, Docker, source)
|
||||
5. **Steps to reproduce**: (exact commands/actions that trigger the issue)
|
||||
6. **Error logs**: (paste relevant logs from the console)
|
||||
7. **Expected behavior**: (what should happen)
|
||||
|
||||
This will help us debug and resolve your issue faster. 🙏
|
||||
```
|
||||
|
||||
### 4. Label the issue
|
||||
|
||||
Add appropriate labels: `needs-info`, `bug`, `enhancement`, `question`, etc.
|
||||
|
||||
```bash
|
||||
gh issue edit <NUMBER> --add-label "needs-info"
|
||||
```
|
||||
166
.claude/commands/resolve-issues-cc.md
Normal file
166
.claude/commands/resolve-issues-cc.md
Normal file
@@ -0,0 +1,166 @@
|
||||
---
|
||||
description: Fetch all open GitHub issues, analyze bugs, resolve what's possible, triage the rest, wait for user validation, then commit and release
|
||||
---
|
||||
|
||||
# /resolve-issues — Automated Issue Resolution Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open issues from the project's GitHub repository, classifies them, analyzes bugs, proposes a resolution plan, waits for user validation, and ONLY THEN implements the fixes, commits, and closes the issues on the current release branch (`release/vX.Y.Z`). It does NOT merge or release automatically — the release branch is later merged via PR to main.
|
||||
|
||||
> **BRANCH RULE**: All work MUST happen on the current `release/vX.Y.Z` branch. Never create separate `fix/` branches. If no release branch exists yet, create one first using `/generate-release` Phase 1 steps 1–5.
|
||||
|
||||
> **⛔ PR PROHIBITION**: If a fix is associated with a contributor's PR, you MUST merge their PR — NEVER close it and re-implement the fix yourself. See `/review-prs` workflow for the full policy. The `gh pr close` command is FORBIDDEN unless the repository owner explicitly requests it.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
// turbo
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch All Open Issues
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh issue list` can be truncated by the tool, silently hiding issues. You MUST use the two-step approach below to guarantee **all** issues are fetched.
|
||||
|
||||
**Step 3a — Get Issue numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh issue list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one issue number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each Issue** (one call per issue):
|
||||
|
||||
- For each issue number from step 3a, run:
|
||||
`gh issue view <NUMBER> --repo <owner>/<repo> --json number,title,labels,body,comments,createdAt,author`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
- Sort by oldest first (FIFO).
|
||||
|
||||
### 4. Classify Each Issue
|
||||
|
||||
For each issue, determine its type:
|
||||
|
||||
- **Bug** — Has `bug` label, or body contains error messages, stack traces, "doesn't work", "broken", "crash", "error"
|
||||
- **Feature Request** — Has `enhancement`/`feature` label, or body describes new functionality
|
||||
- **Question** — Has `question` label, or is asking "how to" something
|
||||
- **Other** — Anything else
|
||||
|
||||
Focus ONLY on **Bugs** for resolution. Feature requests and questions should be skipped with a note in the final report.
|
||||
|
||||
### 5. Deep-Read Each Bug Issue (One-by-One Analysis)
|
||||
|
||||
**IMPORTANT**: Read each bug issue thoroughly, one at a time, before moving to the next. This is NOT a batch process — each issue needs focused attention.
|
||||
|
||||
#### 5a. Understand the Problem
|
||||
|
||||
For each bug issue, perform the full analysis:
|
||||
|
||||
1. **Read the entire body** — including Description, Steps to Reproduce, Expected/Actual Behavior, Error Logs, and Screenshots
|
||||
2. **Read ALL comments** — including bot triage comments (Kilo, etc.) and owner/community responses. Pay attention to:
|
||||
- Whether someone already responded with a fix
|
||||
- Whether a community member confirmed the issue is resolved
|
||||
- Whether the issue was marked as duplicate by a bot. **WARNING: DO NOT blindly trust bot duplicate labels (e.g., kilo-duplicate). Bots make mistakes. You MUST read the full conversation and do your own independent analysis to determine if it is truly a duplicate or a distinct bug.**
|
||||
3. **Identify the claimed error** — extract the exact error message, status code, and provider/model involved
|
||||
|
||||
#### 5b. Check Information Sufficiency
|
||||
|
||||
Verify the issue contains enough to act on:
|
||||
|
||||
- [ ] Clear description of the problem
|
||||
- [ ] Steps to reproduce OR error logs
|
||||
- [ ] Provider/model/version information
|
||||
- [ ] Expected vs actual behavior
|
||||
|
||||
#### 5c. Determine Issue Disposition
|
||||
|
||||
For each bug, classify into one of 5 actions:
|
||||
|
||||
| Disposition | When to Apply | Action |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| **✅ CLOSE — Already Fixed** | Owner responded with fix + no user follow-up, OR community confirmed fix | Close with comment citing which version fixed it |
|
||||
| **✅ CLOSE — Duplicate** | You have independently verified the issue is a duplicate (do NOT rely solely on bot flags) + user provides no new info | Close referencing the original issue |
|
||||
| **✅ CLOSE — Stale** | We requested logs/info > 7 days ago with no reply | Close thanking the user, invite to reopen if needed |
|
||||
| **📝 RESPOND — Needs Info** | Issue is real but missing critical reproduction details | Comment asking for specifics per `/issue-triage` |
|
||||
| **📝 RESPOND — User Config** | Error is caused by unsupported env (Node version, wrong model path, missing API enablement) | Comment explaining the user-side fix |
|
||||
| **🔧 FIX — Code Change** | Root cause is confirmed in the codebase | Research, propose solution in report, wait for approval |
|
||||
|
||||
#### 5d. For "FIX — Code Change" Issues
|
||||
|
||||
Before coding, perform deep source analysis to formulate a plan:
|
||||
|
||||
1. **Search the codebase** — `grep_search` for error strings, relevant function names, affected files
|
||||
2. **Search the web** — for upstream API changes, SDK updates, or breaking changes that explain the bug
|
||||
3. **Read the full source file** — don't rely on grep snippets; understand the surrounding logic
|
||||
4. **Verify the root cause** — confirm the bug is reproducible based on the code, not just a user misconfiguration
|
||||
5. **Formulate a proposed solution** — detail the exact files and lines you will change and how you will solve it.
|
||||
6. **Create an Implementation Plan file** — write your proposed solution to `_tasks/features-vX.Y.Z/<ISSUE_NUMBER>-<short-description>.plan.md` (e.g. `_tasks/features-v3.7.6/1810-auto-restore-probe-failed-db.plan.md`) where `vX.Y.Z` is the current branch version. The plan should contain an Overview, Pre-Implementation Checklist, and detailed Implementation Steps (Files, Changes).
|
||||
7. **DO NOT modify the codebase yet** — wait for user approval on your report and plan first.
|
||||
|
||||
#### 5e. For "RESPOND" Issues
|
||||
|
||||
Post a substantive comment that:
|
||||
|
||||
- Acknowledges the specific error they reported
|
||||
- Explains the likely root cause
|
||||
- Provides concrete steps to resolve (version upgrade, env var fix, model path correction)
|
||||
- Asks for follow-up info if needed
|
||||
|
||||
**Do NOT post generic template responses.** Every comment should reference the user's specific error messages and environment.
|
||||
|
||||
### 6. Generate Report & Wait for Validation
|
||||
|
||||
Present a summary report to the user detailing your proposed actions. For any bugs that need fixing, explicitly explain your proposed solution (files to change and logic) and point out that it will be implemented on the release branch (`release/vX.Y.Z`) after approval.
|
||||
|
||||
| Issue | Title | Status | Proposed Action / Version |
|
||||
| ----- | ----- | ------------- | ----------------------------------------- |
|
||||
| #N | Title | ✅ Close | Already fixed / duplicate (explain why) |
|
||||
| #N | Title | 🔧 Propose | Explanation of the code fix to be applied |
|
||||
| #N | Title | 📝 Respond | Guidance comment to be posted |
|
||||
| #N | Title | ❓ Needs Info | Triage comment to be posted |
|
||||
| #N | Title | ⏭️ Skip | Feature request / not a bug |
|
||||
|
||||
> **⚠️ IMPORTANT**: Do NOT implement code changes, commit, push, or close issues at this step.
|
||||
> Wait for the user to review the proposed fixes and respond with **OK** before proceeding.
|
||||
|
||||
- If the user says **OK** or approves → Proceed to step 7
|
||||
- If the user requests changes → Adjust the proposed solution and present the report again
|
||||
- If the user rejects → Revert any accidental changes and stop
|
||||
|
||||
### 7. Implement Fixes, Run Tests & Commit (only after user approval)
|
||||
|
||||
After the user validates and gives the OK:
|
||||
|
||||
1. **Implement the fixes** — modify the codebase according to the approved plan.
|
||||
2. **Run tests** — `npm run test:all` (or the specific test file) to ensure 100% pass.
|
||||
3. **Update CHANGELOG.md** with all new bug fix entries.
|
||||
4. **Commit** each fix individually on the release branch with message format: `fix: <description> (#<issue_number>)`.
|
||||
5. **Push** the release branch: `git push origin release/vX.Y.Z`.
|
||||
6. **Close resolved issues immediately**. For each issue that was marked as Fixed, run:
|
||||
`gh issue close <NUMBER> --repo <owner>/<repo> --comment "Thank you for reporting! This issue has been fixed and will be included in the next release (vX.Y.Z)."`
|
||||
7. Likewise, close `Duplicate` issues referencing the original, close `Needs Info` if stale, and post the required comments.
|
||||
8. If the project runs automatic releases or needs a PR, proceed to run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user).
|
||||
|
||||
If NO fixes were committed, skip closing and source control steps and just conclude the workflow.
|
||||
120
.claude/commands/review-discussions-cc.md
Normal file
120
.claude/commands/review-discussions-cc.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
description: Read all open GitHub Discussions, summarize them, respond to pending ones, and create issues from actionable feature requests
|
||||
---
|
||||
|
||||
# /review-discussions — GitHub Discussions Review & Response Workflow
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow reads all open GitHub Discussions, generates a categorized summary, identifies which ones need a response, drafts and posts replies, and optionally creates issues from actionable feature requests. It follows the same flow used for Issues but adapted for the Discussions forum.
|
||||
|
||||
> **Tool mapping note (v3.8):** Where steps below say `browser_subagent` (an earlier-runtime tool), in Claude Code use the `gh` CLI via the `Bash` tool — `gh api graphql` for reading discussions and `gh api graphql -F query=...` mutations for posting comments. `WebFetch` is acceptable for read-only HTML scraping when GraphQL is overkill, but prefer `gh` for any write actions.
|
||||
|
||||
// turbo-all
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
- Parse the owner and repo name from the URL
|
||||
|
||||
### 2. Fetch All Open Discussions
|
||||
|
||||
- Use `WebFetch` to fetch `https://github.com/<owner>/<repo>/discussions`
|
||||
- Parse the discussion list to get all discussion titles, IDs, authors, categories, and dates
|
||||
- For each discussion, fetch the individual page to read the full content and all comments/replies
|
||||
|
||||
### 3. Summarize All Discussions
|
||||
|
||||
For each discussion, extract:
|
||||
|
||||
- **Title** and **#Number**
|
||||
- **Author** (GitHub username)
|
||||
- **Category** (Announcements, General, Ideas, Q&A, Show and tell)
|
||||
- **Date** created
|
||||
- **Summary** of the original post (1-2 sentences)
|
||||
- **Comments count** and key participants
|
||||
- **Your previous response** (if any)
|
||||
- **Pending action** — whether a response or follow-up is needed
|
||||
|
||||
### 4. Present Summary Report to User
|
||||
|
||||
Present the full summary to the user organized by category, using a table:
|
||||
|
||||
| # | Category | Title | Author | Date | Status |
|
||||
| --- | -------- | ----- | ------ | ------ | ----------------- |
|
||||
| #N | Ideas | Title | @user | Mar 23 | ⚠️ Needs response |
|
||||
| #N | Q&A | Title | @user | Mar 9 | ✅ Answered |
|
||||
| #N | General | Title | @user | Mar 19 | ⚠️ Needs response |
|
||||
|
||||
Highlight:
|
||||
|
||||
- **⚠️ Needs response** — No reply from maintainer, or a follow-up comment was left unanswered
|
||||
- **✅ Answered** — Maintainer already responded
|
||||
- **🐛 Bug reported** — A bug was mentioned that needs tracking
|
||||
- **💡 Actionable** — Contains a concrete feature request that could become an issue
|
||||
|
||||
### 5. Draft & Post Responses
|
||||
|
||||
For each discussion that needs a response, draft a reply following these guidelines:
|
||||
|
||||
#### Response Style
|
||||
|
||||
- **Friendly and professional** — Start with "Hey @username!"
|
||||
- **Acknowledge the contribution** — Thank the user for their input
|
||||
- **Be specific** — Reference existing features, settings, or dashboard pages if the feature already exists
|
||||
- **Provide workarounds** — If the request isn't implemented yet, suggest current alternatives
|
||||
- **Commit to action** — If the request is valid, state that you'll open an issue or add it to the roadmap
|
||||
- **Keep it concise** — 3-5 paragraphs max
|
||||
|
||||
#### Posting via Browser
|
||||
|
||||
- Use `browser_subagent` to navigate to each discussion and post the comment
|
||||
- **IMPORTANT**: When typing text in GitHub comment boxes via the browser, use only plain ASCII characters:
|
||||
- Use regular hyphens `-` instead of em-dashes
|
||||
- Use `->` instead of arrow symbols
|
||||
- Do NOT use emoji Unicode characters (the browser keyboard may fail on them)
|
||||
- Use `**bold**` and `\`code\`` markdown formatting
|
||||
- Click the green "Comment" button (or "Reply" for threaded replies) after typing
|
||||
- Verify the comment was posted by checking the page shows the new comment
|
||||
|
||||
### 6. Create Issues from Actionable Feature Requests
|
||||
|
||||
For discussions that contain concrete, actionable feature requests:
|
||||
|
||||
1. Ask the user which ones should become issues
|
||||
2. For each approved request, create a GitHub issue via `browser_subagent`:
|
||||
- Navigate to `https://github.com/<owner>/<repo>/issues/new`
|
||||
- **Title**: `<Feature Name> - <Short description>`
|
||||
- **Body** should include:
|
||||
- `## Feature Request` header
|
||||
- `**Source:** Discussion #N by @author`
|
||||
- `## Problem` — What limitation the user hit
|
||||
- `## Proposed Solution` — How it could work
|
||||
- `### Implementation Ideas` — Technical approach
|
||||
- `### Current Workarounds` — What users can do today
|
||||
- `## Additional Context` — Links to related issues/discussions
|
||||
- Add `enhancement` label
|
||||
- Click "Submit new issue" / "Create"
|
||||
3. After creation, go back to the original discussion and post a comment linking to the new issue:
|
||||
- "I've opened Issue #N to track this feature request. Follow along there for updates!"
|
||||
|
||||
### 7. Final Report
|
||||
|
||||
Present a final summary to the user:
|
||||
|
||||
| Discussion | Action Taken |
|
||||
| ---------- | ---------------------------------- |
|
||||
| #N — Title | Responded with workarounds |
|
||||
| #N — Title | Responded + created Issue #N |
|
||||
| #N — Title | Already answered, no action needed |
|
||||
| #N — Title | Responded to follow-up comment |
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow is **interactive** — always present the summary and wait for user approval before posting responses or creating issues
|
||||
- If the user says "pode responder" (or similar approval), proceed with posting all drafted responses
|
||||
- For discussions in non-English languages, respond in the same language as the original post
|
||||
- Always reference specific dashboard paths, config options, or code files when explaining existing features
|
||||
- When a discussion reveals a bug, note it separately from feature requests
|
||||
256
.claude/commands/review-prs-cc.md
Normal file
256
.claude/commands/review-prs-cc.md
Normal file
@@ -0,0 +1,256 @@
|
||||
---
|
||||
description: Analyze open Pull Requests from the project's GitHub repository, generate a critical report, and optionally implement approved changes
|
||||
---
|
||||
|
||||
# /review-prs — PR Review & Analysis Workflow
|
||||
|
||||
## ⛔ ABSOLUTE PROHIBITION — Read Before Anything Else
|
||||
|
||||
> **NEVER close a contributor's PR if you intend to use ANY of their code, ideas, or fixes.**
|
||||
>
|
||||
> **NEVER manually integrate contributor code into a release branch and then close their PR.**
|
||||
>
|
||||
> These actions are **STRICTLY FORBIDDEN** under all circumstances:
|
||||
>
|
||||
> 1. ❌ Closing a PR and cherry-picking/copying its code into a release branch
|
||||
> 2. ❌ Closing a PR "because of conflicts" and re-implementing the same fix yourself
|
||||
> 3. ❌ Closing a PR and committing a "similar" solution inspired by it
|
||||
> 4. ❌ Using `gh pr close` on any PR whose content was or will be used
|
||||
>
|
||||
> **Why**: Closing a PR after taking the contributor's work means they get ZERO credit on GitHub — no "Merged" badge, no contribution graph entry, no public record. This is effectively stealing their contribution. An audit found this happened to **37 PRs** in the past.
|
||||
>
|
||||
> **The ONLY acceptable flow**: Resolve conflicts IN the contributor's branch, push fixes TO their branch, then merge THEIR PR via `gh pr merge`. See Step 7 and Step 8 for the exact procedure.
|
||||
>
|
||||
> **When to close a PR**: ONLY when the user (repository owner) explicitly requests it, OR when the PR is clearly spam/malicious, OR when the author themselves asks to close it. In ALL other cases, leave it open.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow fetches all open PRs from the project's GitHub repository, performs a critical analysis of each one, generates a detailed report, and waits for user approval before proceeding with implementation. **All improvements are committed on the current release branch** (`release/vX.Y.Z`).
|
||||
|
||||
> **BRANCH RULE**: PRs are ALWAYS merged into the current `release/vX.Y.Z` branch, NEVER directly into `main`. The release branch acts as a staging area — only after all PRs are integrated and tests pass does the release branch get merged into `main` via the `/generate-release` workflow.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Identify the GitHub Repository
|
||||
|
||||
- Read `package.json` to get the repository URL, or use the git remote origin URL
|
||||
// turbo
|
||||
- Run: `git -C <project_root> remote get-url origin` to extract the owner/repo
|
||||
|
||||
### 2. Ensure Release Branch Exists
|
||||
|
||||
// turbo
|
||||
|
||||
Before doing any work, ensure you are on the current release branch:
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# If on main, determine next version and create the release branch
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
# Bump patch: e.g. 3.3.11 → 3.3.12
|
||||
NEXT=$(node -p "const [a,b,c]=('$VERSION').split('.').map(Number); c>=9?a+'.'+(b+1)+'.0':a+'.'+b+'.'+(c+1)")
|
||||
git checkout -b release/v$NEXT
|
||||
npm version patch --no-git-tag-version
|
||||
npm install
|
||||
```
|
||||
|
||||
If already on a `release/vX.Y.Z` branch, continue working there.
|
||||
|
||||
### 3. Fetch Open Pull Requests
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: The JSON output of `gh pr list` can be truncated by the tool, silently hiding PRs. You MUST use the two-step approach below to guarantee **all** PRs are fetched.
|
||||
|
||||
**Step 3a — Get PR numbers only** (small output, never truncated):
|
||||
|
||||
- Run: `gh pr list --repo <owner>/<repo> --state open --limit 500 --json number --jq '.[].number'`
|
||||
- This outputs one PR number per line. Count them and confirm total.
|
||||
|
||||
**Step 3b — Fetch full metadata for each PR** (one call per PR):
|
||||
|
||||
- For each PR number from step 3a, run:
|
||||
`gh pr view <NUMBER> --repo <owner>/<repo> --json number,title,author,headRefName,baseRefName,body,createdAt,additions,deletions,files`
|
||||
- You may batch these into parallel calls (up to 4 at a time).
|
||||
|
||||
**Step 3c — Fetch diffs for each PR** (one call per PR, saved to /tmp):
|
||||
|
||||
- For each PR number, run:
|
||||
`gh pr diff <NUMBER> --repo <owner>/<repo> > /tmp/pr<NUMBER>.diff`
|
||||
- Then read each diff file with the `Read` tool.
|
||||
|
||||
- For each open PR, collect:
|
||||
- PR number, title, author, branch, number of commits, date
|
||||
- PR description/body
|
||||
- Files changed (diff)
|
||||
- Existing review comments (from bots or humans)
|
||||
|
||||
**Verification**: Confirm the count of PRs analyzed matches the count from step 3a before proceeding.
|
||||
|
||||
### 3.5 Redirect PR Base Branches to Release Branch
|
||||
|
||||
// turbo-all
|
||||
|
||||
**⚠️ CRITICAL**: Contributors typically open PRs targeting `main`. Before analyzing or merging, redirect ALL open PRs to target the current release branch instead.
|
||||
|
||||
```bash
|
||||
# Get the current release branch name
|
||||
RELEASE_BRANCH=$(git branch --show-current) # e.g. release/v3.5.4
|
||||
|
||||
# For each open PR that targets main, change its base to the release branch
|
||||
for PR_NUM in $(gh pr list --repo <owner>/<repo> --state open --json number,baseRefName --jq '.[] | select(.baseRefName == "main") | .number'); do
|
||||
echo "Redirecting PR #$PR_NUM → $RELEASE_BRANCH"
|
||||
gh pr edit "$PR_NUM" --repo <owner>/<repo> --base "$RELEASE_BRANCH"
|
||||
done
|
||||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
1. PRs merge into the release branch, not directly into `main`
|
||||
2. Merge conflict detection is accurate against the release branch
|
||||
3. The release branch accumulates all changes before the final merge to `main`
|
||||
4. If the release branch doesn't exist on remote yet, push it first: `git push origin $RELEASE_BRANCH`
|
||||
|
||||
### 4. Analyze Each PR — For each open PR, perform the following analysis:
|
||||
|
||||
#### 4a. Feature Assessment
|
||||
|
||||
- **Does it make sense?** Evaluate if the feature fills a real gap or solves a valid problem
|
||||
- **Alignment** — Check if it aligns with the project's architecture and roadmap
|
||||
- **Complexity** — Assess if the scope is reasonable or if it should be split
|
||||
|
||||
#### 4b. Code Quality Review
|
||||
|
||||
- Check for code duplication
|
||||
- Evaluate error handling patterns (consistent with existing codebase?)
|
||||
- Check naming conventions and code style
|
||||
- Verify TypeScript types (any `any` usage, missing types?)
|
||||
|
||||
#### 4c. Security Review
|
||||
|
||||
- Check for missing authentication/authorization on new endpoints
|
||||
- Check for injection vulnerabilities (URL params, SQL, XSS)
|
||||
- Verify input validation on all user-controlled data
|
||||
- Check for hardcoded secrets or credentials
|
||||
|
||||
#### 4d. Architecture Review
|
||||
|
||||
- Does the change follow existing patterns?
|
||||
- Are there any breaking changes to public APIs?
|
||||
- Is the database schema affected? Migration needed?
|
||||
- Impact on performance (N+1 queries, missing indexes?)
|
||||
|
||||
#### 4e. Test Coverage
|
||||
|
||||
- Does the PR include tests?
|
||||
- Are edge cases covered?
|
||||
- Would existing tests break?
|
||||
|
||||
#### 4f. Cross-Layer (Global) Analysis
|
||||
|
||||
Perform a **global impact assessment** to verify whether the PR changes are complete across all layers of the application:
|
||||
|
||||
- **Backend → Frontend check**: If the PR adds or modifies backend-only resources (new endpoints, services, data models), evaluate whether corresponding frontend changes are missing:
|
||||
- Does a new endpoint require a new screen/page in the dashboard?
|
||||
- Should there be a new action button, menu item, or navigation link?
|
||||
- Are there new data fields that should be displayed or editable in the UI?
|
||||
- Does a new feature need a toggle, configuration panel, or status indicator?
|
||||
- **Frontend → Backend check**: If the PR adds frontend elements, verify the backend support exists:
|
||||
- Are the required API endpoints implemented?
|
||||
- Is the data model sufficient for the new UI components?
|
||||
- **Cross-cutting concerns**: Check shared layers (types, DTOs, validation schemas, routes, middleware) for completeness
|
||||
- **Document gaps** — If missing layers are detected, list them as **IMPORTANT** issues in the report with concrete suggestions for what should be added
|
||||
|
||||
### 5. Generate Report — Create a markdown report for each PR including:
|
||||
|
||||
- **PR Summary** — What it does, files affected, commit count
|
||||
- **Improvements/Benefits** — Numbered list with impact level (HIGH/MEDIUM/LOW)
|
||||
- **Risks & Issues** — Categorized as CRITICAL / IMPORTANT / MINOR
|
||||
- **Scoring Table** — Rate across: Feature Relevance, Code Quality, Security, Robustness, Tests
|
||||
- **Verdict** — Ready to merge? With mandatory vs optional fixes
|
||||
- **Next Steps** — What will happen if approved
|
||||
|
||||
### 6. Present to User
|
||||
|
||||
- Show the report in the final response and stop. This is a mandatory checkpoint awaiting explicit user approval before continuing.
|
||||
- Wait for user decision:
|
||||
- **Approved** → Proceed to step 7
|
||||
- **Approved with changes** → Implement the fixes and corrections before merging
|
||||
- **Rejected** → Close the PR or leave a review comment
|
||||
|
||||
### 7. Pre-Merge Fixes & CI Green-Lighting (if approved)
|
||||
|
||||
> **⚠️ Fixes and Conflict Resolutions MUST be pushed back to the PR branch before merging.** We want the PR itself to be green and fully valid before it integrates.
|
||||
|
||||
- **Sync latest fixes & Resolve Conflicts:** Merge the current `release` branch into the PR branch. If there are merge conflicts, you MUST resolve them inside the author's PR branch. NEVER resolve conflicts by closing their PR and doing the work in a separate branch, as this steals credit from the original author.
|
||||
- **Implement improvements:** Apply the required fixes identified in the analysis directly on the PR branch (e.g., adding missing API routes, fixing SSRF, applying comments from other agents).
|
||||
- **Pushing changes to PR branches:**
|
||||
|
||||
```bash
|
||||
# Checkout the PR locally
|
||||
gh pr checkout <NUMBER>
|
||||
|
||||
# Apply fixes, commit your changes
|
||||
git commit -m "chore: apply review suggestions and missing layers"
|
||||
|
||||
# Attempt to push directly to the PR branch
|
||||
git push
|
||||
```
|
||||
|
||||
- **Fallback (ONLY for external forks without maintainer edit access):**
|
||||
Using `cherry-pick` instead of fixing the contributor's PR directly is a **LAST RESORT**. You MUST ALWAYS attempt to `git push` your fixes to their branch first.
|
||||
**ONLY if `git push` explicitly fails with a permission/access error** (meaning the contributor unchecked "Allow edits from maintainers" or it's a locked fork), you may use `git cherry-pick` to bring their changes into the release branch and fix the issues locally.
|
||||
Even then, ensure you preserve the contributor's authorship (`git commit --author="Contributor Name <email>"` if creating new commits).
|
||||
Once you have integrated their work into the release branch, **DO NOT close their PR**. Leave it open so the contributor retains credit. Under NO CIRCUMSTANCES should you use `gh pr close`.
|
||||
|
||||
- Run the project's test suite locally to verify nothing breaks:
|
||||
// turbo
|
||||
- Run: `npm test` or equivalent test command
|
||||
|
||||
### 8. Merge into Release Branch (NEVER CLOSE!)
|
||||
|
||||
> **⚠️ CRITICAL**: NEVER use `gh pr close` for a PR whose idea or code was accepted. Closing a PR in a contributor's face after taking their idea—or closing it just because it had conflicts—is unacceptable.
|
||||
> You MUST ALWAYS resolve conflicts and apply fixes ON THE AUTHOR'S PR BRANCH (unless explicitly locked from edits), and then merge the PR using GitHub so the contributor gets the official "Merged" badge and proper credit on their profile. **Do not use cherry-pick just because it is "easier" than resolving conflicts on their branch.**
|
||||
|
||||
Even if the PR had severe conflicts or required significant architectural adjustments, you MUST:
|
||||
|
||||
1. Resolve any conflicts and apply the fixes directly to their PR branch (as detailed in step 7) or use cherry-picking into the release branch.
|
||||
2. If you managed to fix their branch, merge it into the release branch using the GitHub CLI:
|
||||
`gh pr merge <NUMBER> --repo <owner>/<repo> --squash --body "Integrated into release/vX.Y.Z"`
|
||||
3. If you had to use cherry-picking because you couldn't push to their branch, DO NOT close the PR. GitHub will sometimes auto-detect the cherry-picked commits and mark it as Merged. If it doesn't, leave it open. The repository owner will handle it. NEVER run `gh pr close`.
|
||||
|
||||
In ALL cases:
|
||||
|
||||
- Post a **thank-you comment** on the PR via the GitHub API before or immediately after merging.
|
||||
- The message should:
|
||||
- Thank the author by name/username for their contribution.
|
||||
- Explain what was adjusted or improved (if we pushed fixes to their branch or cherry-picked).
|
||||
- Note it will be included in the upcoming release.
|
||||
- Be friendly, professional, and encouraging.
|
||||
|
||||
> **⚠️ MANDATORY CHANGELOG CREDIT**: When cherry-picking is used (because the PR branch couldn't be pushed to or `gh pr merge` failed), the contributor does NOT get the automatic GitHub "Merged" badge. In this case, you MUST compensate by adding an explicit entry to `CHANGELOG.md` in the `[Unreleased]` section with `(#PR_NUMBER — thanks @username)` format. This ensures the contributor gets public credit in the release notes even if GitHub doesn't auto-detect the cherry-pick. This is NOT optional — skipping it effectively erases the contributor's work from the release record.
|
||||
|
||||
### 9. Sync Local Release Branch
|
||||
|
||||
After merging PRs, sync the local release branch to include the new changes:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git pull origin release/vX.Y.Z
|
||||
```
|
||||
|
||||
### 10. Continue or Finalize
|
||||
|
||||
After processing all approved PRs:
|
||||
|
||||
- If more PRs remain, go back to step 7
|
||||
- When all PRs are processed, **update CHANGELOG.md** on the release branch with all new entries
|
||||
- Run **test coverage** to verify the gate (≥75% statements/lines/functions, ≥70% branches — measured ~82%):
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
- Fix any test regressions introduced by merged PRs
|
||||
- Run `/generate-release` workflow Phase 1 steps 7–10 (tests → commit → push → open PR to main → wait for user)
|
||||
- The `/generate-release` workflow handles the final merge from `release/vX.Y.Z` → `main`
|
||||
341
.claude/commands/version-bump-cc.md
Normal file
341
.claude/commands/version-bump-cc.md
Normal file
@@ -0,0 +1,341 @@
|
||||
---
|
||||
description: Bump version, auto-generate CHANGELOG from git commits, update all versioned files, and refresh root + docs/ documentation to reflect the current project state
|
||||
---
|
||||
|
||||
# Version Bump Workflow
|
||||
|
||||
Automatically bump the project version, generate CHANGELOG entries from git history since the last tag, update every file that references the version, and refresh project documentation to reflect the current state.
|
||||
|
||||
> **VERSION RULE: Always use PATCH bumps (3.x.y → 3.x.y+1)**
|
||||
> NEVER use `npm version minor` or `npm version major`.
|
||||
> Always use: `npm version patch --no-git-tag-version`
|
||||
> The threshold rule: when `y` reaches 10, bump to `3.(x+1).0` — e.g. `3.4.10` → `3.5.0`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Determine Version
|
||||
|
||||
### 1. Read current version and last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Last tag: $LAST_TAG"
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
```
|
||||
|
||||
### 2. Calculate new version
|
||||
|
||||
Apply the patch bump rule:
|
||||
|
||||
- If the current patch number is `9`, the new version is `3.(minor+1).0`
|
||||
- Otherwise, increment patch: `3.x.y` → `3.x.(y+1)`
|
||||
|
||||
If the version was ALREADY bumped (e.g. you are on a release branch and package.json already has the new version), **skip the npm version bump** and use the existing version.
|
||||
|
||||
### 3. Bump package.json (if needed)
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
# Only if version hasn't been bumped yet
|
||||
npm version patch --no-git-tag-version
|
||||
```
|
||||
|
||||
Or for threshold (y=10):
|
||||
|
||||
```bash
|
||||
# Manual threshold bump
|
||||
VERSION="3.X.0" # compute manually
|
||||
npm version "$VERSION" --no-git-tag-version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Generate CHANGELOG from Git History
|
||||
|
||||
### 4. Collect commits since last tag
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||
echo "=== Commits since $LAST_TAG ==="
|
||||
git log "$LAST_TAG"..HEAD --pretty=format:"%h %s" --no-merges | head -100
|
||||
echo ""
|
||||
echo "=== Merge commits ==="
|
||||
git log "$LAST_TAG"..HEAD --merges --pretty=format:"%h %s" | head -50
|
||||
```
|
||||
|
||||
### 5. Classify commits and generate CHANGELOG section
|
||||
|
||||
Analyze each commit message and classify into categories based on the conventional-commit prefix and content:
|
||||
|
||||
| Category | Patterns |
|
||||
| ------------------- | ------------------------------------------------ |
|
||||
| ✨ New Features | `feat:`, `feat(*):` |
|
||||
| 🐛 Bug Fixes | `fix:`, `fix(*):` |
|
||||
| ⚠️ Breaking Changes | `BREAKING CHANGE`, `!:` suffix |
|
||||
| 🛠️ Maintenance | `chore:`, `refactor:`, `perf:`, `build:` |
|
||||
| 🧪 Tests | `test:`, `tests:` |
|
||||
| 📝 Documentation | `docs:` |
|
||||
| 🔒 Security | `security:`, CVE references, vulnerability fixes |
|
||||
| 🌍 i18n | translation updates, locale changes |
|
||||
|
||||
For each category with entries, create a markdown section with descriptive bullet points. Use the commit messages but rewrite them to be human-readable and descriptive (not raw commit messages).
|
||||
|
||||
**If a commit references a PR number** (e.g. `#880`, `PR #885`), include it in the description.
|
||||
|
||||
### 6. Update CHANGELOG.md
|
||||
|
||||
Replace the `## [Unreleased]` section content with the generated entries, then add the new versioned section:
|
||||
|
||||
```markdown
|
||||
## [Unreleased]
|
||||
|
||||
---
|
||||
|
||||
## [NEW_VERSION] — YYYY-MM-DD
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Feature name:** Description (#PR)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **Fix name:** Description (#PR)
|
||||
|
||||
### 🛠️ Maintenance
|
||||
|
||||
- **Item:** Description
|
||||
|
||||
---
|
||||
|
||||
## [PREVIOUS_VERSION] — YYYY-MM-DD
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The date must be today's date in `YYYY-MM-DD` format.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Sync Version Across All Files
|
||||
|
||||
### 7. Update workspace package.json files and openapi.yaml
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
# Update docs/reference/openapi.yaml version
|
||||
sed -i "s/ version: .*/ version: $VERSION/" docs/reference/openapi.yaml
|
||||
echo "✓ docs/reference/openapi.yaml → $VERSION"
|
||||
|
||||
# Update workspace packages (open-sse, electron)
|
||||
for dir in electron open-sse; do
|
||||
if [ -d "$dir" ] && [ -f "$dir/package.json" ]; then
|
||||
(cd "$dir" && npm version "$VERSION" --no-git-tag-version --allow-same-version > /dev/null)
|
||||
echo "✓ $dir/package.json → $VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "✓ All workspace packages synced to $VERSION"
|
||||
```
|
||||
|
||||
### 8. Update llm.txt version references
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
OLD_VERSION_PATTERN='[0-9]\+\.[0-9]\+\.[0-9]\+'
|
||||
|
||||
# Update "Current version:" line
|
||||
sed -i "s/\*\*Current version:\*\* $OLD_VERSION_PATTERN/**Current version:** $VERSION/" llm.txt
|
||||
|
||||
# Update "Key Features (vX.Y.Z)" header
|
||||
sed -i "s/## Key Features (v$OLD_VERSION_PATTERN)/## Key Features (v$VERSION)/" llm.txt
|
||||
|
||||
echo "✓ llm.txt → $VERSION"
|
||||
```
|
||||
|
||||
### 9. Regenerate lock file
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm install
|
||||
echo "✓ Lock file regenerated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Root Documentation
|
||||
|
||||
Based on the CHANGELOG entries generated in Phase 2, review and update these root-level files if relevant changes warrant updates:
|
||||
|
||||
### 10. Review and update root documentation files
|
||||
|
||||
For each file below, read the current content and determine if the CHANGELOG entries require any updates. Only modify files where substantive changes have occurred:
|
||||
|
||||
| File | When to update |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `README.md` | New providers, major features, stats changes (test count, provider count), badges, installation instructions, feature table |
|
||||
| `AGENTS.md` | Architecture changes, new modules, new commands, new providers, new services/handlers/executors |
|
||||
| `CONTRIBUTING.md` | Dev workflow changes, new tooling, test infrastructure changes |
|
||||
| `SECURITY.md` | Security fixes, new auth mechanisms, vulnerability disclosures |
|
||||
| `llm.txt` | Provider count changes, new features, architecture changes |
|
||||
|
||||
**Update rules:**
|
||||
|
||||
- **README.md**: Update provider count, test count, feature highlights table, badges if any numbers changed. If a new provider was added, add it to the provider table. If a major feature was added, add it to the features section.
|
||||
- **AGENTS.md**: If new architecture components (handlers, executors, services, DB modules) were added, update the Architecture section. If new commands were added, update the Build/Test table.
|
||||
- **SECURITY.md**: Add new vulnerability fixes or security improvements to the relevant section.
|
||||
- **llm.txt**: Update provider count, feature list, version references.
|
||||
|
||||
### 11. Review and update docs/ files (excluding i18n/)
|
||||
|
||||
For each file in `docs/` (excluding `docs/i18n/`), review if CHANGELOG changes affect it:
|
||||
|
||||
| File | When to update |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| `docs/reference/API_REFERENCE.md` | New API endpoints, changed request/response formats |
|
||||
| `docs/architecture/ARCHITECTURE.md` | New modules, new services, changed data flow |
|
||||
| `docs/architecture/CODEBASE_DOCUMENTATION.md` | New files, architectural changes, module reorganization |
|
||||
| `docs/architecture/REPOSITORY_MAP.md` | New folders / files / one-line descriptions |
|
||||
| `docs/reference/CLI-TOOLS.md` | New CLI tool integrations, config format changes |
|
||||
| `docs/guides/USER_GUIDE.md` | UX changes, new dashboard pages, settings changes |
|
||||
| `docs/reference/PROVIDER_REFERENCE.md` | New providers (regenerate via `scripts/gen-provider-reference.ts`) |
|
||||
| `docs/frameworks/MCP-SERVER.md` | New MCP tools, changed tool signatures, scope changes |
|
||||
| `docs/frameworks/A2A-SERVER.md` | New A2A skills, protocol changes |
|
||||
| `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | New external agent protocols supported |
|
||||
| `docs/frameworks/CLOUD_AGENT.md` | Cloud agent additions (codex-cloud, devin, jules) or API changes |
|
||||
| `docs/architecture/AUTHZ_GUIDE.md` | New route classifications, policy changes |
|
||||
| `docs/security/GUARDRAILS.md` | New guardrails registered, priority/order changes |
|
||||
| `docs/security/COMPLIANCE.md` | Audit log / retention / no-log policy changes |
|
||||
| `docs/frameworks/SKILLS.md` | Skill framework / registry / built-in skill changes |
|
||||
| `docs/frameworks/MEMORY.md` | Memory pipeline / extraction / injection / Qdrant changes |
|
||||
| `docs/frameworks/EVALS.md` | Evaluation framework changes, new evaluators |
|
||||
| `docs/frameworks/WEBHOOKS.md` | New webhook events, payload schema changes |
|
||||
| `docs/routing/REASONING_REPLAY.md` | Reasoning capture/replay pipeline changes |
|
||||
| `docs/routing/AUTO-COMBO.md` | Routing changes, new strategies, scoring weight changes |
|
||||
| `docs/architecture/RESILIENCE_GUIDE.md` | Circuit breaker / cooldown / lockout behavior changes |
|
||||
| `docs/security/STEALTH_GUIDE.md` | TLS / CLI fingerprint changes |
|
||||
| `docs/ops/TUNNELS_GUIDE.md` | Cloudflare tunnel feature changes |
|
||||
| `docs/guides/ELECTRON_GUIDE.md` | Electron build / signing / packaging changes |
|
||||
| `docs/guides/TROUBLESHOOTING.md` | New known issues, resolved problems |
|
||||
| `docs/ops/RELEASE_CHECKLIST.md` | Process changes |
|
||||
| `docs/ops/COVERAGE_PLAN.md` | Coverage gate adjustments, target metrics |
|
||||
| `docs/reference/openapi.yaml` | Already updated in step 7 |
|
||||
|
||||
**Only update files where the CHANGELOG entries directly affect the documented content.** Do NOT update files just to bump a version number — only when the documented behavior, features, or architecture has actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Verify
|
||||
|
||||
### 12. Run lint check
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 13. Run tests
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
npm test
|
||||
```
|
||||
|
||||
### 14. Verify version sync across all files
|
||||
|
||||
// turbo
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "Expected version: $VERSION"
|
||||
echo ""
|
||||
|
||||
echo "--- package.json ---"
|
||||
grep '"version"' package.json | head -1
|
||||
|
||||
echo "--- open-sse/package.json ---"
|
||||
grep '"version"' open-sse/package.json | head -1
|
||||
|
||||
echo "--- electron/package.json ---"
|
||||
[ -f electron/package.json ] && grep '"version"' electron/package.json | head -1
|
||||
|
||||
echo "--- docs/reference/openapi.yaml ---"
|
||||
grep " version:" docs/reference/openapi.yaml | head -1
|
||||
|
||||
echo "--- llm.txt ---"
|
||||
grep "Current version:" llm.txt
|
||||
|
||||
echo "--- CHANGELOG.md (first versioned entry) ---"
|
||||
grep "^## \[" CHANGELOG.md | head -2
|
||||
```
|
||||
|
||||
### 15. 🛑 STOP — Present Summary to User
|
||||
|
||||
**STOP** and present a summary to the user including:
|
||||
|
||||
- Old version → New version
|
||||
- CHANGELOG entries generated
|
||||
- Files modified
|
||||
- Test results
|
||||
- Any documentation updates made
|
||||
|
||||
**Wait for the user to confirm before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Commit (only after user approval)
|
||||
|
||||
### 16. Stage and commit
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
cd /home/diegosouzapw/dev/proxys/OmniRoute
|
||||
git add -A
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
git commit -m "chore(release): bump to v$VERSION — changelog, docs, version sync"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This workflow does **NOT** create tags, releases, or deploy. Use `/generate-release` for the full release cycle after this.
|
||||
- This workflow does **NOT** update `docs/i18n/` translations. Translation updates are handled manually or via release tooling — the `/update-i18n` command does not currently exist as a Claude Code slash command.
|
||||
- The CHANGELOG generation is based on git commits since the last tag. If there are no new commits, the workflow should inform the user and stop.
|
||||
- Always verify the generated CHANGELOG entries make sense — raw commit messages may need rewriting for clarity.
|
||||
- If the version was already bumped (e.g. you're on a `release/vX.Y.Z` branch), skip the `npm version` step and use the existing version.
|
||||
|
||||
## Version Touchpoints Checklist
|
||||
|
||||
| File | Field/Pattern |
|
||||
| ----------------------------- | ----------------------------------------------------------- |
|
||||
| `package.json` | `"version": "X.Y.Z"` |
|
||||
| `open-sse/package.json` | `"version": "X.Y.Z"` |
|
||||
| `electron/package.json` | `"version": "X.Y.Z"` |
|
||||
| `docs/reference/openapi.yaml` | `version: X.Y.Z` |
|
||||
| `llm.txt` | `**Current version:** X.Y.Z` and `## Key Features (vX.Y.Z)` |
|
||||
| `CHANGELOG.md` | `## [X.Y.Z] — YYYY-MM-DD` |
|
||||
BIN
.codegraph/codegraph.db
Normal file
BIN
.codegraph/codegraph.db
Normal file
Binary file not shown.
@@ -9,7 +9,6 @@
|
||||
# Dependencies and build output
|
||||
node_modules
|
||||
.next
|
||||
.build
|
||||
out
|
||||
build
|
||||
dist
|
||||
@@ -42,13 +41,11 @@ blob-report
|
||||
# Issue #2348: The Dashboard Docs viewer reads markdown from `/app/docs` at
|
||||
# runtime. The previous `docs/*` block hid every file except openapi.yaml,
|
||||
# so the in-product help screen failed with ENOENT for every page.
|
||||
# We now keep the English markdown tree plus the docs assets imported by MDX
|
||||
# during `next build`, while still dropping the bulky translated docs and
|
||||
# extra raster diagram sources that account for most of the docs footprint
|
||||
# of the ~50 MB docs directory. The Docs viewer reads the default-locale
|
||||
# (English) sources at runtime, so translations are not required in the
|
||||
# container image.
|
||||
# Translations (~51 MB) are excluded — the Docs viewer reads English sources.
|
||||
# v3.8.3+ (fumadocs-mdx): `npm run build` webpack-bundles docs — keep assets referenced
|
||||
# from English markdown (docs/diagrams/exported/*.svg, docs/screenshots/*.png).
|
||||
docs/i18n/**
|
||||
# Raster sources under docs/diagrams/ only (exported SVGs are required at build time).
|
||||
docs/diagrams/**/*.png
|
||||
docs/diagrams/**/*.jpg
|
||||
docs/diagrams/**/*.jpeg
|
||||
|
||||
241
.env.example
241
.env.example
@@ -6,6 +6,7 @@
|
||||
# │ Reference: docs/ENVIRONMENT.md for full details and usage scenarios. │
|
||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 1. REQUIRED SECRETS — Must be set before first run!
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -36,8 +37,6 @@ INITIAL_PASSWORD=CHANGEME
|
||||
# Base directory for all persistent data (SQLite DB, logs, backups).
|
||||
# Used by: src/lib/db/core.ts — resolves the SQLite database file path.
|
||||
# Default: ~/.omniroute/ | Override for Docker or custom installations.
|
||||
# Hint: When running in Docker, consider mounting a host directory here for data persistence across container restarts
|
||||
# also if you want to share the same database as "npm run dev" use "./data"
|
||||
# DATA_DIR=/var/lib/omniroute
|
||||
|
||||
# Encryption key for SQLite database encryption at rest.
|
||||
@@ -61,6 +60,7 @@ DISABLE_SQLITE_AUTO_BACKUP=false
|
||||
# Default: redis://localhost:6379 (or redis://redis:6379 in Docker)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 3. NETWORK & PORTS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -157,11 +157,6 @@ OMNIROUTE_USE_TURBOPACK=1
|
||||
# Values: production | development | Default: production
|
||||
NODE_ENV=production
|
||||
|
||||
# Container runtime — controls startup script behavior (permissions, advice).
|
||||
# Values: docker | podman | Default: docker
|
||||
# Set to "podman" when running under rootless Podman so the entrypoint
|
||||
# gives the correct fix instructions (podman unshare chown vs sudo chown).
|
||||
CONTAINER_HOST=docker
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 4. SECURITY & AUTHENTICATION
|
||||
@@ -200,14 +195,6 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# Generate: openssl rand -base64 32
|
||||
# OMNIROUTE_WS_BRIDGE_SECRET=
|
||||
|
||||
# Per-process secret that proves the trusted peer-IP stamp came from OmniRoute's
|
||||
# own HTTP server (scripts/dev/peer-stamp.mjs). The custom server stamps the real
|
||||
# TCP peer IP as `<token>|<ip>`; the authz middleware trusts the locality only
|
||||
# when the token matches. Used by: src/server/authz/policies/management.ts.
|
||||
# Auto-generated per boot — leave UNSET in normal use. Only set it to pin a fixed
|
||||
# value across processes (e.g. a multi-process setup that must share the stamp).
|
||||
# OMNIROUTE_PEER_STAMP_TOKEN=
|
||||
|
||||
# Comma-separated API key IDs that skip request logging (GDPR/compliance).
|
||||
# Used by: src/lib/compliance/index.ts — suppresses logs for specific keys.
|
||||
# NO_LOG_API_KEY_IDS=key_abc123,key_def456
|
||||
@@ -241,6 +228,7 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# When unset, OmniRoute uses the per-feature defaults. Set to "false"/"0" to disable.
|
||||
# OUTBOUND_SSRF_GUARD_ENABLED=true
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -259,20 +247,13 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# Used by: src/middleware/promptInjectionGuard.ts — extends injection guard.
|
||||
# PII_REDACTION_ENABLED=false
|
||||
|
||||
# Minimum streaming window size for PII detection (bytes). Default: 200.
|
||||
# Used by: src/lib/streamingPiiTransform.ts.
|
||||
# PII_WINDOW_SIZE=200
|
||||
|
||||
# Test bypass: allow setting PII_WINDOW_SIZE below minimum. Default: false.
|
||||
# Used by: src/lib/streamingPiiTransform.ts.
|
||||
# PII_TEST_BYPASS_MIN_WINDOW=false
|
||||
|
||||
# ── Response-Side: PII Sanitizer ──
|
||||
# Scans LLM responses for leaked PII before returning to the client.
|
||||
# Used by: src/lib/piiSanitizer.ts
|
||||
# PII_RESPONSE_SANITIZATION=false
|
||||
# PII_RESPONSE_SANITIZATION_MODE=redact # redact = mask PII | warn = log only | block = drop response
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 6. TOOL & ROUTING POLICIES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -292,6 +273,7 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# Default: 5000 | Minimum: 1000
|
||||
# OMNIROUTE_PAYLOAD_RULES_RELOAD_MS=5000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 7. URLS & CLOUD SYNC
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -362,8 +344,7 @@ NEXT_PUBLIC_CLOUD_URL=
|
||||
#OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/
|
||||
#OMNIROUTE_GEMINI_CLI_USAGE_URL=https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist
|
||||
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
|
||||
#OMNIROUTE_OPENCODE_QUOTA_URL=https://opencode.ai/zen/go/v1/quota
|
||||
#OMNIROUTE_OPENCODE_GO_QUOTA_URL=https://api.z.ai/api/monitor/usage/quota/limit
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 8. OUTBOUND PROXY (Upstream Provider Calls)
|
||||
@@ -392,6 +373,7 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# Used by: open-sse/services/claudeTurnstileSolver.ts
|
||||
# OMNIROUTE_TURNSTILE_IGNORE_TLS_ERRORS=false
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 9. CLI TOOL INTEGRATION
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -421,6 +403,7 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# CLI_QODER_BIN=qoder
|
||||
# CLI_QWEN_BIN=qwen
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 10. INTERNAL AGENT & MCP INTEGRATIONS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -470,12 +453,6 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# Default: 70
|
||||
PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
|
||||
|
||||
# Gap (ms) between consecutive OAuth quota fetches in a bulk provider-limits sync.
|
||||
# OAuth providers are fetched one at a time with this spacing so a single host
|
||||
# never bursts simultaneous usage/refresh requests to the same upstream. Set to 0
|
||||
# to opt out (restores fully concurrent fetches). Default: 1500
|
||||
PROVIDER_LIMITS_SYNC_SPACING_MS=1500
|
||||
|
||||
# Disable all background services (sync, pricing, model refresh).
|
||||
# Used by: src/instrumentation-node.ts, src/lib/initCloudSync.ts
|
||||
# Useful for: CI builds, test environments, or resource-constrained containers.
|
||||
@@ -551,11 +528,6 @@ PROVIDER_LIMITS_SYNC_SPACING_MS=1500
|
||||
# Default: empty (use upstream-reported credits).
|
||||
#ANTIGRAVITY_CREDITS=
|
||||
|
||||
# Override the path to the Antigravity CLI (agy) token file read by the
|
||||
# "auto-detect local login" import. Used by:
|
||||
# src/app/api/providers/agy-auth/apply-local/route.ts — for non-standard installs.
|
||||
# Default: ~/.gemini/antigravity-cli/antigravity-oauth-token
|
||||
#AGY_TOKEN_FILE=
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 11. OAUTH PROVIDER CREDENTIALS
|
||||
@@ -573,23 +545,6 @@ CLAUDE_OAUTH_CLIENT_ID=9d1c250a-e61b-44d9-88ed-5944d1962f5e
|
||||
# ── Codex / OpenAI ──
|
||||
CODEX_OAUTH_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann
|
||||
|
||||
# Milliseconds to wait between consecutive Codex token refreshes.
|
||||
# Used by: open-sse/services/refreshSerializer.ts. Default: 0 (no spacing).
|
||||
# CODEX_REFRESH_SPACING_MS=0
|
||||
|
||||
# ── Trae (ByteDance) ──
|
||||
# Trae stream idle timeout (ms). Default: 300000 (5 min).
|
||||
# Used by: open-sse/executors/trae.ts.
|
||||
# TRAE_STREAM_TIMEOUT_MS=300000
|
||||
|
||||
# Trae OAuth token override. Used by: open-sse/executors/trae.ts.
|
||||
# TRAE_TOKEN=
|
||||
|
||||
# ── The Old LLM (theoldllm) ──
|
||||
# Playwright navigation timeout (ms) for the browser-backed token capture.
|
||||
# Used by: open-sse/executors/theoldllm.ts. Default: 30000 (30s).
|
||||
# THEOLDLLM_NAV_TIMEOUT_MS=30000
|
||||
|
||||
# ── Gemini / Gemini CLI / Antigravity / Windsurf (all Google-based) ──
|
||||
# These providers ship public OAuth client_id/secret values (or Firebase Web
|
||||
# keys) embedded in their public CLIs/binaries. Defaults are baked into the
|
||||
@@ -708,6 +663,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
# CLI_USER_ID= # legacy alias for OMNIROUTE_USER_ID
|
||||
# SERVER_URL= # legacy alias for OMNIROUTE_SERVER
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 12. PROVIDER USER-AGENT OVERRIDES
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -716,15 +672,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
|
||||
# Update these when providers release new CLI versions to avoid blocks.
|
||||
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.158 (external, cli)"
|
||||
|
||||
# Disable the deterministic tool-name cloak applied on both Anthropic-bound paths
|
||||
# (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) —
|
||||
# third-party-harness tool names are aliased to
|
||||
# Claude Code canonical or PascalCase forms so Anthropic does not refuse the
|
||||
# stream with a misleading 400 out-of-extra-usage placeholder. Set to true to
|
||||
# forward the original names verbatim (debugging only).
|
||||
# CLAUDE_DISABLE_TOOL_NAME_CLOAK=false
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.146 (external, cli)"
|
||||
CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
|
||||
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
|
||||
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
|
||||
@@ -743,6 +691,7 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
|
||||
# CODEX_CLIENT_VERSION=0.132.0
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -771,6 +720,7 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
#KIMI_CLI_VERSION=1.36.0
|
||||
#KIMI_CODING_DEVICE_ID=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 14. API KEY PROVIDERS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -793,6 +743,7 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# OpenAI/Mistral/Together/Fireworks/NVIDIA configured via Dashboard → Providers
|
||||
# also work for embeddings.
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 15. TIMEOUT SETTINGS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -839,22 +790,6 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# OMNIROUTE_PPLX_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_PPLX_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Grok web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/grokTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged.
|
||||
# OMNIROUTE_GROK_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_GROK_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Browser-backed web-cookie chat (Playwright shared pool) ──
|
||||
# Used by: open-sse/services/browserPool.ts + browserBackedChat.ts. The shared
|
||||
# browser pool warms a headless context for web-cookie providers (e.g. claude-web)
|
||||
# that need a real browser to satisfy anti-bot challenges. Set OMNIROUTE_BROWSER_POOL=off
|
||||
# to fully disable the pool; set WEB_COOKIE_USE_BROWSER=1 to opt a web-cookie chat
|
||||
# request into the browser-backed path.
|
||||
# OMNIROUTE_BROWSER_POOL=on
|
||||
# WEB_COOKIE_USE_BROWSER=0
|
||||
|
||||
# ── Circuit breaker thresholds and reset windows ──
|
||||
# Used by: open-sse/config/constants.ts → src/lib/resilience/settings.ts.
|
||||
# Defaults match historical PROVIDER_PROFILES values (post-scaling for
|
||||
@@ -887,6 +822,7 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
|
||||
# Default: 30000 (30 seconds)
|
||||
# SHUTDOWN_TIMEOUT_MS=30000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 16. LOGGING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -960,30 +896,15 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 100000
|
||||
# PROXY_LOGS_TABLE_MAX_ROWS=100000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 17. MEMORY OPTIMIZATION (Low-RAM / Docker)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Node.js V8 heap limit in MB, passed to the server via --max-old-space-size.
|
||||
# Used by the standalone launcher (Docker CMD) and `omniroute serve`.
|
||||
# Clamped to [64, 16384]. Default: 512 (safe for a 1 GB / 1 core VPS). Size it to
|
||||
# roughly half the box's RAM, leaving the rest for native memory (better-sqlite3,
|
||||
# buffers — ~300 MB) and the OS:
|
||||
# 1 GB RAM → 512 (default)
|
||||
# 2 GB RAM → 1024
|
||||
# 4 GB RAM → 2048
|
||||
# In a memory-capped container, set this EXPLICITLY: Node reads the HOST's RAM,
|
||||
# not the cgroup limit, so leaving it to a RAM heuristic can oversize the heap and
|
||||
# get the container OOM-killed. (#2939)
|
||||
# OMNIROUTE_MEMORY_MB=512
|
||||
|
||||
# Heap-pressure shed threshold (MB) — chatCore returns 503 when V8 heapUsed exceeds
|
||||
# it, to avoid hard OOM under concurrent large-context load.
|
||||
# LEAVE UNSET: it now AUTO-CALIBRATES to 85% of the actual V8 heap ceiling, so it
|
||||
# tracks OMNIROUTE_MEMORY_MB above and never sits below the ~260 MB runtime baseline
|
||||
# (a fixed 200 here used to reject every request). Used by: open-sse/utils/heapPressure.ts.
|
||||
# Override only to hand-tune for a known workload.
|
||||
# HEAP_PRESSURE_THRESHOLD_MB=
|
||||
# Node.js V8 heap limit in MB.
|
||||
# Used by: Docker entrypoint — sets --max-old-space-size.
|
||||
# Default: 256 (Docker) | system default (npm)
|
||||
# OMNIROUTE_MEMORY_MB=256
|
||||
|
||||
# ── CLI helpers (bin/cli/) ──
|
||||
# Override UI language for CLI output. Accepts BCP-47 locale (e.g. en, pt-BR).
|
||||
@@ -1009,10 +930,6 @@ APP_LOG_TO_FILE=true
|
||||
# Default: ~/.omniroute/plugins/ Override in dev/CI to point at a local plugin tree.
|
||||
# OMNIROUTE_PLUGIN_PATH=
|
||||
|
||||
# Allow plugins to request the 'exec' permission (spawn child processes from the
|
||||
# plugin worker sandbox). Disabled by default; set to 1 to enable (local operator only).
|
||||
# OMNIROUTE_PLUGINS_ALLOW_EXEC=0
|
||||
|
||||
# ── Prompt cache (system prompt deduplication) ──
|
||||
# Used by: open-sse/services — caches identical system prompts across requests.
|
||||
# PROMPT_CACHE_MAX_SIZE=50 # Max cached entries (default: 50)
|
||||
@@ -1039,6 +956,7 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: open-sse/utils/usageTracking.ts
|
||||
# USAGE_TOKEN_BUFFER=100
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 18. PRICING SYNC
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1054,6 +972,7 @@ APP_LOG_TO_FILE=true
|
||||
# Comma-separated data sources. Default: litellm
|
||||
# PRICING_SYNC_SOURCES=litellm
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 19. MODEL SYNC (Dev)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1062,6 +981,7 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 86400 (24 hours)
|
||||
# MODELS_DEV_SYNC_INTERVAL=86400
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 20. PROVIDER-SPECIFIC SETTINGS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1072,14 +992,6 @@ APP_LOG_TO_FILE=true
|
||||
# Default: 86400000 (24 hours)
|
||||
# OPENROUTER_CATALOG_TTL_MS=86400000
|
||||
|
||||
# ── Model catalog response shape ──
|
||||
# Include display-friendly name fields in /v1/models responses.
|
||||
# Disable for clients that expect model IDs only.
|
||||
# Defined in: src/shared/constants/featureFlagDefinitions.ts
|
||||
# Used by: src/app/api/v1/models/catalog.ts
|
||||
# Default: true
|
||||
# MODEL_CATALOG_INCLUDE_NAMES=true
|
||||
|
||||
# ── NanoBanana (Image Generation) ──
|
||||
# Polling config for async image generation jobs.
|
||||
# Used by: open-sse/handlers/imageGeneration.ts
|
||||
@@ -1150,6 +1062,7 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: open-sse/config/providerRegistry.ts — allows Docker service names.
|
||||
# LOCAL_HOSTNAMES=omlx,mlx-audio
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 21. PROXY HEALTH
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1175,6 +1088,7 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: src/lib/tokenHealthCheck.ts. Default: 3000.
|
||||
# HEALTHCHECK_STAGGER_MS=3000
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 22. DEBUGGING
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1184,13 +1098,6 @@ APP_LOG_TO_FILE=true
|
||||
# CURSOR_STREAM_DEBUG is kept as a backward-compatible alias.
|
||||
# Used by: open-sse/executors/cursor.ts
|
||||
# CURSOR_DEBUG=1
|
||||
|
||||
# Enable verbose trace logging for OmniRoute internals.
|
||||
# Used by: open-sse/handlers/chatCore.ts.
|
||||
# OMNIROUTE_TRACE=true
|
||||
|
||||
# Standard DEBUG flag (same effect as OMNIROUTE_TRACE).
|
||||
# DEBUG=true
|
||||
# CURSOR_STREAM_DEBUG=1
|
||||
|
||||
# When CURSOR_DEBUG=1, also append raw decoded chunks to this file path.
|
||||
@@ -1200,16 +1107,6 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: open-sse/executors/cursor.ts.
|
||||
# CURSOR_STREAM_TIMEOUT_MS=300000
|
||||
|
||||
# Cursor tool-commit directive toggle. Default-on: when a request declares
|
||||
# tools, a directive is prepended so composer-2.5 reliably issues tool calls
|
||||
# instead of narrating intent. Set to 0 to disable.
|
||||
# Used by: open-sse/executors/cursor.ts.
|
||||
# CURSOR_TOOL_DIRECTIVE=1
|
||||
|
||||
# Per-image fetch timeout (ms) for remote image_url vision input. Default: 15000.
|
||||
# Used by: open-sse/utils/cursorImages.ts.
|
||||
# CURSOR_IMAGE_FETCH_TIMEOUT_MS=15000
|
||||
|
||||
# Cursor state DB path override (for cursor version detection).
|
||||
# Used by: open-sse/utils/cursorVersionDetector.ts. Default: probed automatically.
|
||||
# CURSOR_STATE_DB_PATH=
|
||||
@@ -1234,6 +1131,7 @@ APP_LOG_TO_FILE=true
|
||||
# Enable E2E test mode — relaxes auth and enables test harness hooks.
|
||||
# NEXT_PUBLIC_OMNIROUTE_E2E_MODE=true
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 23. GITHUB INTEGRATION (Issue Reporting)
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1251,6 +1149,7 @@ APP_LOG_TO_FILE=true
|
||||
# GITHUB_ISSUES_TOKEN when unset.
|
||||
# GITHUB_TOKEN=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 24. PROVIDER QUOTAS, TUNNELS & SANDBOXED SKILLS
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1283,13 +1182,6 @@ APP_LOG_TO_FILE=true
|
||||
# Used by: src/app/api/providers/command-code/auth/shared.ts.
|
||||
# COMMAND_CODE_CALLBACK_PORT=
|
||||
|
||||
# ── Command Code CLI version header ──
|
||||
# Value sent as the x-command-code-version header to the Command Code upstream.
|
||||
# Overrides the built-in default; bump if the upstream requires a newer CLI version.
|
||||
# Used by: open-sse/executors/commandCode.ts
|
||||
# Default: 0.33.2
|
||||
# COMMAND_CODE_VERSION=0.33.2
|
||||
|
||||
# ── MITM debug proxy (development only) ──
|
||||
# Used by: src/mitm/server.cjs — captures upstream traffic for inspection.
|
||||
# MITM_LOCAL_PORT=443
|
||||
@@ -1362,6 +1254,7 @@ APP_LOG_TO_FILE=true
|
||||
# SKILLS_SANDBOX_NETWORK_ENABLED=0
|
||||
# SKILLS_ALLOWED_SANDBOX_IMAGES=
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 25. TEST & E2E
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1383,12 +1276,6 @@ APP_LOG_TO_FILE=true
|
||||
# Disable the OAuth token healthcheck loop during tests (default: true).
|
||||
# OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=true
|
||||
|
||||
# Exclude specific providers from the PROACTIVE token-refresh sweep (comma-separated,
|
||||
# case-insensitive). Targeted alternative to OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK: keeps
|
||||
# rotating-cascade providers (Codex/OpenAI share one Auth0 family) on the reactive 401
|
||||
# path only, while short-TTL providers like Kimi-coding keep being refreshed proactively.
|
||||
# OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS=codex,openai
|
||||
|
||||
# Silence healthcheck noise in Playwright stdout (default: true).
|
||||
# OMNIROUTE_HIDE_HEALTHCHECK_LOGS=true
|
||||
|
||||
@@ -1414,31 +1301,6 @@ APP_LOG_TO_FILE=true
|
||||
# Number of parallel translation requests (default 4).
|
||||
# OMNIROUTE_TRANSLATION_CONCURRENCY=4
|
||||
|
||||
# ─── Cloud Sync hardening (v3.8.6) ──────────────────────────────────────────
|
||||
# Shared secret used to verify the HMAC-SHA256 of the Cloud sync response body
|
||||
# (the Cloud endpoint must sign each response with the same secret and place
|
||||
# the hex digest in the X-Cloud-Sig header). When unset, v3.8.6 logs a warning
|
||||
# but accepts unsigned responses for back-compat. v3.9 will make this required.
|
||||
# OMNIROUTE_CLOUD_SYNC_SECRET=
|
||||
#
|
||||
# Set to "true" to allow the Cloud Sync endpoint to overwrite local OAuth
|
||||
# tokens (accessToken / refreshToken / providerSpecificData). Default OFF —
|
||||
# only non-credential metadata is synced. See docs/security/SOCKET_DEV_FINDINGS.md §5.
|
||||
# OMNIROUTE_CLOUD_SYNC_SECRETS=false
|
||||
|
||||
# ─── Zed import legacy compat (v3.8.6) ──────────────────────────────────────
|
||||
# Set to "true" to fall back to the v3.8.5 one-step "import everything from
|
||||
# the keychain" behaviour. Default OFF — the new 2-step confirmation flow
|
||||
# requires `confirmedAccounts` in the request body. See SOCKET_DEV_FINDINGS.md §2.
|
||||
# OMNIROUTE_ZED_IMPORT_LEGACY_ONE_STEP=false
|
||||
|
||||
# ─── Build profile (build-time only) ────────────────────────────────────────
|
||||
# Set to "minimal" before `npm run build` to physically remove four optional
|
||||
# privileged modules (MITM cert install, Zed keychain import, Cloud Sync,
|
||||
# 9router installer) from the standalone bundle. The resulting artifact is
|
||||
# intended to be published as `omniroute-secure`. See SECURITY.md.
|
||||
# OMNIROUTE_BUILD_PROFILE=full
|
||||
|
||||
# Electron smoke harness (used by scripts/dev/smoke-electron-packaged.mjs).
|
||||
# ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login
|
||||
# ELECTRON_SMOKE_TIMEOUT_MS=45000
|
||||
@@ -1447,52 +1309,3 @@ APP_LOG_TO_FILE=true
|
||||
# ELECTRON_SMOKE_DATA_DIR=
|
||||
# ELECTRON_SMOKE_KEEP_DATA=0
|
||||
# ELECTRON_SMOKE_STREAM_LOGS=0
|
||||
|
||||
# Playground Studio
|
||||
# Default model used by the improve-prompt route (optional; falls back to model in request body).
|
||||
PLAYGROUND_IMPROVE_PROMPT_DEFAULT_MODEL=
|
||||
# Maximum number of parallel compare columns in the Compare tab.
|
||||
PLAYGROUND_COMPARE_MAX_COLUMNS=4
|
||||
# Memory engine (plan 21)
|
||||
# MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # default 5 min
|
||||
# MEMORY_EMBEDDING_CACHE_MAX=1000 # default 1000 entries
|
||||
# MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2
|
||||
# MEMORY_STATIC_MODEL=minishlab/potion-base-8M # HF repo id (download once)
|
||||
# MEMORY_STATIC_CACHE_DIR= # default <DATA_DIR>/embeddings
|
||||
# MEMORY_VEC_TOP_K=20 # default top-K for vector search
|
||||
# MEMORY_RRF_K=60 # RRF k constant (sqlite-vec hybrid recipe)
|
||||
# HF_HUB_ENDPOINT=https://huggingface.co # override Hugging Face Hub base URL for static potion downloads
|
||||
# AgentBridge + Traffic Inspector (Group A)
|
||||
|
||||
# AgentBridge
|
||||
AGENTBRIDGE_UPSTREAM_CA_CERT=
|
||||
|
||||
# Inspector
|
||||
INSPECTOR_BUFFER_SIZE=1000
|
||||
INSPECTOR_HTTP_PROXY_PORT=8080
|
||||
INSPECTOR_HTTP_PROXY_AUTOSTART=false
|
||||
INSPECTOR_TLS_INTERCEPT=false
|
||||
INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES=30
|
||||
INSPECTOR_MAX_BODY_KB=1024
|
||||
INSPECTOR_MASK_SECRETS=true
|
||||
INSPECTOR_LLM_HOSTS_EXTRA=
|
||||
INSPECTOR_INTERNAL_INGEST_TOKEN=
|
||||
# Quota Sharing (Group B — planos 16+22)
|
||||
QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# QUOTA_STORE_REDIS_URL= # ex.: redis://localhost:6379 (apenas quando driver=redis)
|
||||
# QUOTA_SATURATION_THRESHOLD=0.5 # 0..1; >= threshold ativa modo strict (sem empréstimo)
|
||||
# QUOTA_SOFT_DEPRIORITIZE_FACTOR=0.7 # 0..1; multiplicador do score quando soft policy ativa
|
||||
# QUOTA_CONSUMPTION_RETENTION_DAYS=14 # GC de buckets quota_consumption.updated_at antigos
|
||||
|
||||
# ─── OpenCode config regeneration (scripts/ad-hoc/regen-opencode-config.ts) ───
|
||||
# Base URL of the OmniRoute instance to query for /v1/models when regenerating
|
||||
# an opencode.json with accurate limit.context values. Used by:
|
||||
# scripts/ad-hoc/regen-opencode-config.ts. Default: http://localhost:20128
|
||||
# OMNIROUTE_URL=
|
||||
# API key to authenticate against the OmniRoute /v1/models endpoint. Falls back
|
||||
# to OPENCODE_API_KEY when unset. Used by: scripts/ad-hoc/regen-opencode-config.ts.
|
||||
# OMNIROUTE_KEY=
|
||||
# OpenCode-style API key (sk-...) for the regenerated opencode.json. Used by:
|
||||
# scripts/ad-hoc/regen-opencode-config.ts. Falls back to OMNIROUTE_KEY.
|
||||
# OPENCODE_API_KEY=
|
||||
|
||||
|
||||
167
.github/workflows/ci.yml
vendored
167
.github/workflows/ci.yml
vendored
@@ -24,12 +24,6 @@ jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# tsx gates below (known-symbols, route-guard-membership) import modules that
|
||||
# open SQLite on load; provide DB env so a fresh CI DB initializes cleanly.
|
||||
JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-lint-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
@@ -43,17 +37,6 @@ jobs:
|
||||
- run: npm run check:cycles
|
||||
- run: npm run check:route-validation:t06
|
||||
- run: npm run check:any-budget:t11
|
||||
- run: npm run check:provider-consistency
|
||||
- run: npm run check:fetch-targets
|
||||
- run: npm run check:deps
|
||||
- run: npm run check:file-size
|
||||
- run: npm run check:error-helper
|
||||
- run: npm run check:migration-numbering
|
||||
- run: npm run check:public-creds
|
||||
- run: npm run check:db-rules
|
||||
- run: npm run check:known-symbols
|
||||
- run: npm run check:route-guard-membership
|
||||
- run: npm run check:test-discovery
|
||||
- run: npm run check:docs-sync
|
||||
- run: npm run typecheck:core
|
||||
# typecheck:noimplicit:core is a forward-looking gate (noImplicitAny).
|
||||
@@ -62,46 +45,6 @@ jobs:
|
||||
- run: npm run typecheck:noimplicit:core
|
||||
continue-on-error: true
|
||||
|
||||
quality-gate:
|
||||
name: Quality Ratchet
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-coverage
|
||||
if: ${{ always() && needs.test-coverage.result == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
# Coverage mergeada (coverage-summary.json) p/ o ratchet de cobertura.
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage/
|
||||
- run: npm run quality:collect
|
||||
# Catraca: falha se qualquer métrica regredir vs quality-baseline.json (commitado).
|
||||
# Hoje: contagem de warnings do ESLint. Fase 4 estende com cobertura (lida do
|
||||
# coverage mergeado). Tamanho de arquivo e duplicação têm gates dedicados.
|
||||
- name: Ratchet check
|
||||
run: node scripts/quality/check-quality-ratchet.mjs --summary .artifacts/quality-ratchet.md
|
||||
# Catraca de duplicação (jscpd@4 sobre src+open-sse). Roda neste job (paralelo)
|
||||
# para não pesar no caminho crítico do lint.
|
||||
- name: Duplication ratchet
|
||||
run: npm run check:duplication
|
||||
- name: Complexity ratchet
|
||||
run: npm run check:complexity
|
||||
- name: Append summary
|
||||
if: always()
|
||||
run: cat .artifacts/quality-ratchet.md >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload ratchet report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: quality-ratchet
|
||||
path: .artifacts/quality-ratchet.md
|
||||
if-no-files-found: warn
|
||||
|
||||
docs-sync-strict:
|
||||
name: Docs Sync (Strict)
|
||||
runs-on: ubuntu-latest
|
||||
@@ -113,19 +56,6 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:docs-all
|
||||
# Previously-orphaned contract gates (existed as files, never wired anywhere).
|
||||
# All exit 0 today: cli-i18n is a hard gate, openapi-coverage is a ratchet
|
||||
# (floor ~36), openapi-security-tiers is advisory (Hard Rules #15/#17).
|
||||
- name: CLI i18n consistency
|
||||
run: npm run check:cli-i18n
|
||||
- name: OpenAPI route coverage (ratchet)
|
||||
run: npm run check:openapi-coverage
|
||||
- name: OpenAPI security-tier consistency (advisory)
|
||||
run: npm run check:openapi-security-tiers
|
||||
- name: OpenAPI spec paths resolve to real routes (anti-hallucination)
|
||||
run: npm run check:openapi-routes
|
||||
- name: Doc /api refs resolve to real routes (anti-hallucination)
|
||||
run: npm run check:docs-symbols
|
||||
- name: i18n translation drift (warn)
|
||||
run: node scripts/i18n/check-translation-drift.mjs --warn
|
||||
|
||||
@@ -195,9 +125,6 @@ jobs:
|
||||
run: git fetch --no-tags origin "${GITHUB_BASE_REF}" --depth=1
|
||||
- name: Validate source changes include tests
|
||||
run: node scripts/check/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
|
||||
# Anti test-masking: flag net assert removal / new assert.ok(true) in changed tests.
|
||||
- name: Detect test-masking (weakened assertions)
|
||||
run: npm run check:test-masking
|
||||
- name: Publish PR test policy summary
|
||||
if: always()
|
||||
run: |
|
||||
@@ -217,21 +144,6 @@ jobs:
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
- name: Archive Next.js build for E2E shards
|
||||
# Use tar so the archive preserves paths relative to CWD (.build/next/...).
|
||||
# upload-artifact path-stripping is ambiguous when exclude patterns are used;
|
||||
# an explicit tar avoids the double-nesting issue (.build/next/next/...).
|
||||
run: |
|
||||
tar -czf /tmp/e2e-build.tar.gz \
|
||||
--exclude='.build/next/standalone/node_modules' \
|
||||
--exclude='.build/next/cache' \
|
||||
.build/next
|
||||
- name: Upload Next.js build for E2E shards
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e-next-build
|
||||
path: /tmp/e2e-build.tar.gz
|
||||
retention-days: 1
|
||||
|
||||
package-artifact:
|
||||
name: Package Artifact
|
||||
@@ -247,11 +159,7 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
# build:cli runs a clean build into .build/next and assembles dist/
|
||||
# For release builds prefer: npm run build:release (clean rebuild + HEAD sentinel)
|
||||
- run: npm run build:cli
|
||||
- name: Assert dist/server.js exists
|
||||
run: test -f dist/server.js || (echo "dist/server.js missing — build:cli did not assemble correctly" && exit 1)
|
||||
- run: npm run check:pack-artifact
|
||||
|
||||
electron-package-smoke:
|
||||
@@ -303,32 +211,7 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: node --max-old-space-size=4096 --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts "tests/unit/{api,auth,authz,build,cli,cli-helper,compression,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts"
|
||||
|
||||
test-vitest:
|
||||
name: Vitest (MCP / autoCombo / UI components)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: build
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.CI_NODE_VERSION }}
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
# The second test runner (CLAUDE.md: "Both test runners must pass") — was never
|
||||
# wired into CI until the 2026-06-09 quality audit (Fase 6A.2).
|
||||
- run: npm run test:vitest
|
||||
# vitest:ui is RED today (14 fails — UI component drift accumulated while the
|
||||
# suite never ran in CI). Informational until the Fase 6A triage (2026-06-16+)
|
||||
# fixes the components/tests; then drop continue-on-error to make it blocking.
|
||||
- run: npm run test:vitest:ui
|
||||
continue-on-error: true
|
||||
- run: node --max-old-space-size=4096 --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts
|
||||
|
||||
node-24-compat:
|
||||
name: Node 24 Compatibility (${{ matrix.shard }}/2)
|
||||
@@ -352,7 +235,7 @@ jobs:
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts "tests/unit/{api,auth,authz,build,cli,cli-helper,compression,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts"
|
||||
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||
|
||||
node-26-compat:
|
||||
name: Node 26 Compatibility (${{ matrix.shard }}/2)
|
||||
@@ -376,7 +259,7 @@ jobs:
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- run: npm run build
|
||||
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts "tests/unit/{api,auth,authz,build,cli,cli-helper,compression,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts"
|
||||
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
||||
|
||||
test-coverage-shard:
|
||||
name: Coverage Shard (${{ matrix.shard }}/8)
|
||||
@@ -415,7 +298,7 @@ jobs:
|
||||
--exclude=tests/** \
|
||||
--exclude=**/*.test.* \
|
||||
node --max-old-space-size=4096 --import tsx --test --test-force-exit --test-concurrency=4 \
|
||||
--test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts "tests/unit/{api,auth,authz,build,cli,cli-helper,compression,cors,dashboard,db,db-adapters,docs,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts"
|
||||
--test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts
|
||||
- name: Upload raw shard coverage
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -441,16 +324,12 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Download all shard coverage
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
pattern: coverage-shard-*
|
||||
path: coverage-shards/
|
||||
merge-multiple: true
|
||||
- name: Merge + report + gate
|
||||
# Merging 8 shards of raw v8 coverage is memory-heavy; the default Node
|
||||
# heap OOMs (exit 134). Raise it for the c8 merge/report step.
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
run: |
|
||||
mkdir -p coverage
|
||||
if [ ! -d coverage-shards ] || ! find coverage-shards -maxdepth 1 -type f -name '*.json' | grep -q .; then
|
||||
@@ -458,12 +337,6 @@ jobs:
|
||||
find . -maxdepth 3 -type f | sort
|
||||
exit 1
|
||||
fi
|
||||
# Gate aligned to the project's local coverage bar: `npm run test:coverage`
|
||||
# gates at 60/60/60/60, so CI must match it (the previous CI floor of 40
|
||||
# silently undershot the local bar — a real drift). Real merged coverage is
|
||||
# ~79/79/82/75, so 60 is a conservative floor with headroom; the Fase-4
|
||||
# coverage ratchet (quality-baseline.json) layers "must not drop vs baseline"
|
||||
# on top of this floor.
|
||||
npx c8 report \
|
||||
--temp-directory coverage-shards \
|
||||
--reports-dir coverage \
|
||||
@@ -474,7 +347,7 @@ jobs:
|
||||
--exclude=tests/** \
|
||||
--exclude=**/*.test.* \
|
||||
--check-coverage \
|
||||
--statements 60 --lines 60 --functions 60 --branches 60
|
||||
--statements 75 --lines 75 --functions 75 --branches 70
|
||||
- name: Build coverage summary
|
||||
if: always()
|
||||
run: |
|
||||
@@ -613,18 +486,14 @@ jobs:
|
||||
}
|
||||
|
||||
test-e2e:
|
||||
name: E2E Tests (${{ matrix.shard }}/9)
|
||||
name: E2E Tests (${{ matrix.shard }}/6)
|
||||
runs-on: ubuntu-latest
|
||||
# Build artifact from the `build` job is downloaded instead of rebuilding
|
||||
# (~5min saved per shard). 9 shards (up from 6) reduces tests per shard by
|
||||
# ~33%. Playwright browser is cached across runs (~1.5min saved per shard).
|
||||
# Heavy shard target: ≤20min (was ~40min). Timeout 45min to cover slow runners.
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 20
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
shard: [1, 2, 3, 4, 5, 6]
|
||||
env:
|
||||
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
||||
API_KEY_SECRET: ci-test-api-key-secret-long
|
||||
@@ -638,23 +507,9 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run check:node-runtime
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-chromium-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: playwright-chromium-${{ runner.os }}-
|
||||
- run: npx playwright install --with-deps chromium
|
||||
- name: Download Next.js build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: e2e-next-build
|
||||
path: /tmp/
|
||||
- name: Extract Next.js build and restore standalone node_modules
|
||||
run: |
|
||||
tar -xzf /tmp/e2e-build.tar.gz
|
||||
cp -r node_modules .build/next/standalone/node_modules
|
||||
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/9
|
||||
- run: npm run build
|
||||
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/6
|
||||
|
||||
test-integration:
|
||||
name: Integration Tests (${{ matrix.shard }}/2)
|
||||
|
||||
77
.github/workflows/deploy-vps.yml
vendored
77
.github/workflows/deploy-vps.yml
vendored
@@ -17,82 +17,27 @@ jobs:
|
||||
name: Deploy OmniRoute to VPS
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check VPS SSH reachability from runner
|
||||
id: reach
|
||||
env:
|
||||
# Pass the host via env (never interpolate a secret straight into the
|
||||
# script body) so /dev/tcp gets a shell variable, not inlined text.
|
||||
VPS_HOST: ${{ secrets.VPS_HOST }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
# A GitHub-hosted runner can only deploy when it can actually open a TCP
|
||||
# connection to the VPS SSH port. The Local VPS lives on a private LAN and
|
||||
# the Akamai host firewalls :22 to known IPs, so the runner is routinely
|
||||
# unable to reach it (`dial tcp ***:22: i/o timeout`). Treat "unreachable
|
||||
# from the runner" as a SKIP — the real deploys are run manually from an
|
||||
# allowed network via the deploy-vps-local / deploy-vps-akamai skills — so
|
||||
# an unreachable host no longer red-fails every release/push pipeline.
|
||||
# When the host IS reachable, the deploy step below still runs in full and
|
||||
# its health gate surfaces any genuine deploy failure.
|
||||
if timeout 15 bash -c 'exec 3<>"/dev/tcp/${VPS_HOST}/22"' 2>/dev/null; then
|
||||
echo "reachable=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ VPS_HOST:22 reachable from the runner — proceeding with deploy."
|
||||
else
|
||||
echo "reachable=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning title=Auto-deploy skipped::VPS_HOST:22 is not reachable from this GitHub runner (private LAN / firewalled). Deploy manually with the deploy-vps-local or deploy-vps-akamai skill."
|
||||
fi
|
||||
|
||||
- name: Deploy via SSH
|
||||
if: steps.reach.outputs.reachable == 'true'
|
||||
uses: appleboy/ssh-action@v1
|
||||
continue-on-error: true
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: 22
|
||||
timeout: 60s
|
||||
command_timeout: 15m
|
||||
timeout: 30s
|
||||
command_timeout: 5m
|
||||
script: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Updating OmniRoute ==="
|
||||
npm install -g omniroute@latest
|
||||
INSTALLED_VERSION=$(omniroute --version 2>/dev/null | tr -d '[:space:]' || echo "unknown")
|
||||
echo "Installed CLI version: $INSTALLED_VERSION"
|
||||
npm install -g omniroute@latest 2>&1
|
||||
INSTALLED_VERSION=$(omniroute --version 2>/dev/null || echo "unknown")
|
||||
echo "Installed version: $INSTALLED_VERSION"
|
||||
|
||||
# Recreate the PM2 process instead of `pm2 restart`. A bare restart
|
||||
# re-runs whatever script path was saved earlier; after the build-output
|
||||
# reorg (app/ -> dist/, .next -> .build/next) a process pinned to the old
|
||||
# app/server-ws.mjs path can no longer start, and the node process dies
|
||||
# while PM2 still reports "online" — so the box never binds :20128.
|
||||
# Always launch via the `omniroute` bin so .env is loaded and the dist/
|
||||
# layout is resolved correctly.
|
||||
echo "=== (Re)creating PM2 process via bin ==="
|
||||
pm2 delete omniroute 2>/dev/null || true
|
||||
pm2 start omniroute --name omniroute -- --port 20128
|
||||
echo "=== Restarting PM2 ==="
|
||||
pm2 restart omniroute || pm2 start omniroute --name omniroute -- --port 20128
|
||||
pm2 save
|
||||
|
||||
# Health gate: fail the deploy unless the box actually reports healthy.
|
||||
# Poll /api/monitoring/health for "status":"healthy" (a deeper signal than
|
||||
# a static page 200 — it confirms the app booted, not just that a port is
|
||||
# bound). Boot can take a while after a native-module/build-layout change,
|
||||
# so poll up to ~3min before giving up.
|
||||
echo "=== Health Check (gates the deploy) ==="
|
||||
ok=0
|
||||
for i in $(seq 1 36); do
|
||||
BODY=$(curl -sf -m 5 http://localhost:20128/api/monitoring/health 2>/dev/null || true)
|
||||
if printf '%s' "$BODY" | grep -q '"status":"healthy"'; then
|
||||
ok=1
|
||||
echo "✅ /api/monitoring/health -> healthy (attempt $i) — version $INSTALLED_VERSION"
|
||||
break
|
||||
fi
|
||||
echo "… not healthy yet (attempt $i/36), retrying in 5s"
|
||||
sleep 5
|
||||
done
|
||||
if [ "$ok" != "1" ]; then
|
||||
echo "❌ Health check failed — /api/monitoring/health never reported healthy after ~3min"
|
||||
echo "--- recent PM2 logs ---"
|
||||
pm2 logs omniroute --lines 40 --nostream || true
|
||||
exit 1
|
||||
fi
|
||||
echo "=== Health Check ==="
|
||||
sleep 3
|
||||
curl -sf http://localhost:20128/api/settings > /dev/null && echo "✅ OmniRoute is healthy" || echo "❌ Health check failed"
|
||||
echo "=== Deploy complete ==="
|
||||
|
||||
222
.github/workflows/docker-publish.yml
vendored
222
.github/workflows/docker-publish.yml
vendored
@@ -30,13 +30,9 @@ permissions:
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: Resolve Docker release metadata
|
||||
docker:
|
||||
name: Build and Push Docker (multi-arch)
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
promote_latest: ${{ steps.version.outputs.promote_latest }}
|
||||
skip: ${{ steps.version.outputs.skip }}
|
||||
env:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
steps:
|
||||
@@ -118,213 +114,77 @@ jobs:
|
||||
|
||||
echo "Publishing diegosouzapw/omniroute:$VERSION (promote_latest=$PROMOTE, skip=$SKIP)"
|
||||
|
||||
build:
|
||||
name: Build Docker (${{ matrix.platform }})
|
||||
needs: prepare
|
||||
if: needs.prepare.outputs.skip != 'true'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-24.04
|
||||
arch: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
arch: arm64
|
||||
env:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
GHCR_IMAGE_NAME: ghcr.io/diegosouzapw/omniroute
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }}
|
||||
fetch-depth: 0
|
||||
- name: Set up QEMU (for multi-arch builds)
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push platform image by digest
|
||||
id: build
|
||||
- name: Compute image tags
|
||||
id: tags
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
PROMOTE_LATEST: ${{ steps.version.outputs.promote_latest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAGS="${IMAGE_NAME}:${VERSION}"
|
||||
TAGS="${TAGS}"$'\n'"ghcr.io/diegosouzapw/omniroute:${VERSION}"
|
||||
if [ "$PROMOTE_LATEST" = "true" ]; then
|
||||
TAGS="${TAGS}"$'\n'"${IMAGE_NAME}:latest"
|
||||
TAGS="${TAGS}"$'\n'"ghcr.io/diegosouzapw/omniroute:latest"
|
||||
fi
|
||||
{
|
||||
echo "tags<<EOF"
|
||||
echo "$TAGS"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "Tags to push:"
|
||||
echo "$TAGS"
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: runner-base
|
||||
platforms: ${{ matrix.platform }}
|
||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}
|
||||
${{ env.GHCR_IMAGE_NAME }}
|
||||
cache-from: type=gha,scope=docker-${{ matrix.arch }}
|
||||
cache-to: type=gha,scope=docker-${{ matrix.arch }},mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
no-cache: false
|
||||
env:
|
||||
DOCKER_BUILDKIT_INLINE_CACHE: 1
|
||||
|
||||
- name: Build and push WEB platform image by digest
|
||||
id: build-web
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
target: runner-web
|
||||
platforms: ${{ matrix.platform }}
|
||||
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}
|
||||
${{ env.GHCR_IMAGE_NAME }}
|
||||
cache-from: type=gha,scope=docker-web-${{ matrix.arch }}
|
||||
cache-to: type=gha,scope=docker-web-${{ matrix.arch }},mode=max
|
||||
no-cache: false
|
||||
env:
|
||||
DOCKER_BUILDKIT_INLINE_CACHE: 1
|
||||
|
||||
- name: Export digests
|
||||
env:
|
||||
DIGEST_BASE: ${{ steps.build.outputs.digest }}
|
||||
DIGEST_WEB: ${{ steps.build-web.outputs.digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p /tmp/digests/base /tmp/digests/web
|
||||
touch "/tmp/digests/base/${DIGEST_BASE#sha256:}"
|
||||
touch "/tmp/digests/web/${DIGEST_WEB#sha256:}"
|
||||
|
||||
- name: Upload base digests
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-base-${{ matrix.arch }}
|
||||
path: /tmp/digests/base/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload web digests
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-web-${{ matrix.arch }}
|
||||
path: /tmp/digests/web/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Publish multi-arch manifests
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
if: needs.prepare.outputs.skip != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
IMAGE_NAME: diegosouzapw/omniroute
|
||||
GHCR_IMAGE_NAME: ghcr.io/diegosouzapw/omniroute
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
PROMOTE_LATEST: ${{ needs.prepare.outputs.promote_latest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download base digests
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: digests-base-*
|
||||
path: /tmp/digests/base
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download web digests
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: digests-web-*
|
||||
path: /tmp/digests/web
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create Docker Hub manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
create_manifest() {
|
||||
local image="$1" suffix="$2" dir="$3"
|
||||
local tags=(-t "${image}:${VERSION}${suffix}")
|
||||
if [ "$PROMOTE_LATEST" = "true" ]; then
|
||||
tags+=(-t "${image}:latest${suffix}")
|
||||
fi
|
||||
local refs=()
|
||||
while IFS= read -r digest_file; do
|
||||
refs+=("${image}@sha256:$(basename "$digest_file")")
|
||||
done < <(find "$dir" -type f | sort)
|
||||
if [ "${#refs[@]}" -eq 0 ]; then
|
||||
echo "No image digests in $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
|
||||
}
|
||||
|
||||
create_manifest "${IMAGE_NAME}" "" /tmp/digests/base
|
||||
create_manifest "${IMAGE_NAME}" "-web" /tmp/digests/web
|
||||
|
||||
- name: Create GHCR manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
create_manifest() {
|
||||
local image="$1" suffix="$2" dir="$3"
|
||||
local tags=(-t "${image}:${VERSION}${suffix}")
|
||||
if [ "$PROMOTE_LATEST" = "true" ]; then
|
||||
tags+=(-t "${image}:latest${suffix}")
|
||||
fi
|
||||
local refs=()
|
||||
while IFS= read -r digest_file; do
|
||||
refs+=("${image}@sha256:$(basename "$digest_file")")
|
||||
done < <(find "$dir" -type f | sort)
|
||||
if [ "${#refs[@]}" -eq 0 ]; then
|
||||
echo "No image digests in $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
|
||||
}
|
||||
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "" /tmp/digests/base
|
||||
create_manifest "${GHCR_IMAGE_NAME}" "-web" /tmp/digests/web
|
||||
|
||||
- name: Inspect image
|
||||
if: needs.prepare.outputs.version != 'main'
|
||||
if: steps.version.outputs.skip != 'true' && steps.version.outputs.version != 'main'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
docker buildx imagetools inspect "${IMAGE_NAME}:${VERSION}"
|
||||
|
||||
- name: Update Docker Hub description
|
||||
# Only refresh README/description when we actually promote :latest
|
||||
# (avoids overwriting from main pushes or back-fill builds).
|
||||
if: needs.prepare.outputs.promote_latest == 'true'
|
||||
if: steps.version.outputs.skip != 'true' && steps.version.outputs.promote_latest == 'true'
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
|
||||
13
.github/workflows/electron-release.yml
vendored
13
.github/workflows/electron-release.yml
vendored
@@ -136,16 +136,9 @@ jobs:
|
||||
|
||||
- name: Smoke packaged Electron app
|
||||
if: matrix.platform != 'linux'
|
||||
# Best-effort smoke on Windows + macos-arm64:
|
||||
# - Windows: requestSingleInstanceLock() fails due to USERPROFILE
|
||||
# sanitization needed for the build step.
|
||||
# - macos-arm64: the headless GitHub arm64 runner crashes Electron's GPU
|
||||
# process (gpu_process_host exit_code=15 → network service crash →
|
||||
# "No rendezvous client, terminating process"), so the app can't bind
|
||||
# 127.0.0.1:20128 in time. The identical bundle is smoke-gated on
|
||||
# macos-intel + linux, so packaging is still verified per-OS; we don't
|
||||
# let the arm64 runner's GPU flakiness block the desktop release.
|
||||
continue-on-error: ${{ matrix.platform == 'windows' || matrix.platform == 'macos-arm64' }}
|
||||
# Windows CI: requestSingleInstanceLock() fails due to USERPROFILE
|
||||
# sanitization needed for the build step. Smoke is best-effort there.
|
||||
continue-on-error: ${{ matrix.platform == 'windows' }}
|
||||
env:
|
||||
ELECTRON_SMOKE_TIMEOUT_MS: 60000
|
||||
ELECTRON_SMOKE_STREAM_LOGS: "1"
|
||||
|
||||
6
.github/workflows/opencode-plugin-ci.yml
vendored
6
.github/workflows/opencode-plugin-ci.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
node: ["22", "24"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: npm
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
cache-dependency-path: "@omniroute/opencode-plugin/package-lock.json"
|
||||
- run: npm install --no-audit --no-fund
|
||||
- run: npm run build
|
||||
- uses: actions/upload-artifact@v7
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: opencode-plugin-dist
|
||||
path: "@omniroute/opencode-plugin/dist"
|
||||
|
||||
94
.gitignore
vendored
94
.gitignore
vendored
@@ -4,25 +4,6 @@
|
||||
.omnivscodeagent/
|
||||
omnirouteCloud/
|
||||
omnirouteSite/
|
||||
_cache/
|
||||
_ideia/
|
||||
_mono_repo/
|
||||
_references/
|
||||
_tasks/
|
||||
.agents/**
|
||||
.claude/**
|
||||
.gemini/**
|
||||
.config/**
|
||||
.data/**
|
||||
.logs/**
|
||||
.tests/**
|
||||
.coverage/**
|
||||
coverage/
|
||||
.dist/**
|
||||
.next/**
|
||||
.build/**
|
||||
.out/**
|
||||
|
||||
|
||||
# Memory Bank and Cursor rules (local-only AI agent context)
|
||||
memory-bank/
|
||||
@@ -34,7 +15,6 @@ memory-bank/
|
||||
.claude/scheduled_tasks/
|
||||
.claude/sessions/
|
||||
.claude/state.json
|
||||
.claude/settings.local.json
|
||||
|
||||
# Root-level underscore-prefixed directories (private/draft — never commit)
|
||||
/_*/
|
||||
@@ -44,15 +24,32 @@ docs/new-features/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
*.map
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
.data/
|
||||
.next-playwright/
|
||||
|
||||
# testing
|
||||
coverage/
|
||||
coverage**
|
||||
|
||||
# next.js
|
||||
.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
/app
|
||||
cloud/*
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# Obsidian sync plugin — committed for community distribution
|
||||
!obsidian-plugin/
|
||||
obsidian-plugin/node_modules/
|
||||
|
||||
# Serena AI assistant config (local-only tool, not project code)
|
||||
.serena/
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
@@ -63,9 +60,6 @@ yarn-error.log*
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env.example
|
||||
# Provider API keys (never commit)
|
||||
*.api-key
|
||||
.nvidia-api-key
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -117,13 +111,10 @@ app.__qa_backup/
|
||||
.app-build-backup-*/
|
||||
backup/
|
||||
|
||||
# Build intermediates (.build/) and shippable standalone (dist/).
|
||||
# These are fully reproducible from source; never committed.
|
||||
# Layer 1: Next.js now writes to .build/next (was .next); assembled bundle → dist/
|
||||
# (Previously /app/ was the standalone output; renamed to /dist/ in Layer 1.)
|
||||
/.build/
|
||||
/dist/
|
||||
/.next/
|
||||
# Production standalone build (created by scripts/prepublish.mjs)
|
||||
# Conflicts with Next.js App Router detection in dev (root app/ shadows src/app/)
|
||||
# npm publish still includes it via package.json "files" field
|
||||
/app/
|
||||
|
||||
# Electron
|
||||
electron/dist-electron/
|
||||
@@ -138,6 +129,9 @@ vscode-extension/
|
||||
*.sqlite-wal
|
||||
*.sqlite-journal
|
||||
|
||||
# Compiled npm-package build artifact (not source, should not be in git)
|
||||
/app
|
||||
|
||||
# IDEA
|
||||
.idea/
|
||||
|
||||
@@ -187,27 +181,3 @@ scripts/i18n/_pending-keys.json
|
||||
|
||||
# Fumadocs generated source
|
||||
.source/
|
||||
|
||||
# AI agent local settings and configs
|
||||
.agents/
|
||||
.antigravitycli/
|
||||
.claude/
|
||||
|
||||
# PR Reviews and local feedback files
|
||||
pr_reviews*.json
|
||||
|
||||
#hidden local data directories (never commit)
|
||||
.local-data/
|
||||
.data-dev/
|
||||
/.junie/
|
||||
|
||||
# internal setup prompts with personal credentials — never commit
|
||||
CODEX-SETUP-PROMPT.md
|
||||
# Quality ratchet — métricas efêmeras (baseline é commitado, métricas não)
|
||||
quality-metrics.json
|
||||
-home-diegosouzapw-dev-automações-bots-yt-downloader-20260504 .txt
|
||||
-home-diegosouzapw-dev-automações-bots-yt-downloader-20260410 .txt
|
||||
docs/prompts/AGENT-OWNERSHIP-PROTOCOL.omniroute.md
|
||||
docs/prompts/AGENT-OWNERSHIP-PROTOCOL.md
|
||||
docs/prompts/AGENT-OWNERSHIP-PROTOCOL.omniroute-mim.md
|
||||
docs/prompts/AGENT-OWNERSHIP-PROTOCOL.omniroute-mid.md
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
#!/usr/bin/env sh
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "⚠️ npx not found in PATH — skipping pre-commit hooks"
|
||||
echo " Run 'npm run lint && npm run check:any-budget:t11' manually before pushing."
|
||||
exit 0
|
||||
fi
|
||||
# #!/usr/bin/env sh
|
||||
# if ! command -v npx >/dev/null 2>&1; then
|
||||
# echo "⚠️ npx not found in PATH — skipping pre-commit hooks"
|
||||
# echo " Run 'npm run lint && npm run check:any-budget:t11' manually before pushing."
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
# Cheap, deterministic local gates (re-enabled). Slower checks (i18n drift,
|
||||
# openapi coverage/security-tiers, env-doc sync) run in CI to keep commits fast.
|
||||
npx lint-staged
|
||||
node scripts/check/check-docs-sync.mjs
|
||||
npm run check:any-budget:t11
|
||||
# npx lint-staged
|
||||
# node scripts/check/check-docs-sync.mjs
|
||||
# npm run check:any-budget:t11
|
||||
|
||||
# # Strict env-doc sync (FASE 2)
|
||||
# node scripts/check/check-env-doc-sync.mjs
|
||||
|
||||
# # CLI i18n consistency check — all t() keys must exist in en.json (FASE 8.3)
|
||||
# node scripts/check/check-cli-i18n.mjs
|
||||
|
||||
# # i18n docs drift advisory (FASE 5) — warn-only on pre-commit; CI enforces strict.
|
||||
# node scripts/i18n/check-translation-drift.mjs --warn || \
|
||||
# echo "⚠️ i18n drift detected. Run 'npm run i18n:run' to update locale mirrors."
|
||||
|
||||
# # i18n UI coverage advisory (FASE 6) — pre-commit warns; CI enforces strict.
|
||||
# node scripts/i18n/check-ui-keys-coverage.mjs --threshold=80 || \
|
||||
# echo "⚠️ UI i18n coverage below 80% for at least one locale."
|
||||
|
||||
# # OpenAPI coverage check — fails if coverage < 99% (FASE 08 content audit)
|
||||
# node scripts/check/check-openapi-coverage.mjs
|
||||
|
||||
# # OpenAPI security tier consistency check — fails if x-loopback-only / x-always-protected
|
||||
# # annotations diverge from routeGuard.ts compile-time constants (FASE 08 content audit)
|
||||
# node scripts/check/check-openapi-security-tiers.mjs
|
||||
|
||||
219
.i18n-state.json
Normal file
219
.i18n-state.json
Normal file
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"sources": {
|
||||
"CLAUDE.md": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"locales": {
|
||||
"pt-BR": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "301b997e936b1d476e6094042666b96b33f42a4372fd2d9ccf904aacbfd7f023",
|
||||
"updated_at": "2026-05-22T20:13:39.165Z"
|
||||
},
|
||||
"az": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "c26844ec50b2abfbb002767fb5c6c9c3982ec65789435bbc134bf1a7b50bf84a",
|
||||
"updated_at": "2026-05-22T20:13:39.166Z"
|
||||
},
|
||||
"bn": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "0b1416ec3b5af8cc6415d12a9ff12ce078acca4a7bb52a7422ebde3b6ae22832",
|
||||
"updated_at": "2026-05-22T20:13:39.166Z"
|
||||
},
|
||||
"ar": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "bc747c5ea2a387dd37dea9dc1337d9734f2ec0da38a7134573d9743e3f4d2aef",
|
||||
"updated_at": "2026-05-22T20:13:39.167Z"
|
||||
},
|
||||
"cs": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "e37dce45820b53be9d3d5ead08cadb8d13e4c77597b3009bb0be4e485917f5c6",
|
||||
"updated_at": "2026-05-22T20:13:39.167Z"
|
||||
},
|
||||
"da": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "645c382bebe0931eaad6747e33667fd094b92b3f4042549b953db798de6b1f46",
|
||||
"updated_at": "2026-05-22T20:13:39.168Z"
|
||||
},
|
||||
"de": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "b6558f82eb67676baf4d78946a8d1e1b808f7763e5ebaf9f57948cbd1641dc0f",
|
||||
"updated_at": "2026-05-22T20:13:39.168Z"
|
||||
},
|
||||
"es": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "afb2a2dfa74a74c3105b0ac70181d33f5eefd201efe7edb6aba0740864639fe5",
|
||||
"updated_at": "2026-05-22T20:13:39.169Z"
|
||||
},
|
||||
"fa": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "58966769aeb71d23c55cf4aeb452f2bbe32036df8fba1d7596f3e861897d507b",
|
||||
"updated_at": "2026-05-22T20:13:39.169Z"
|
||||
},
|
||||
"fi": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "04cf72a3b370edcb3d54361c6cc250b3af227ba183376827006c7998839740aa",
|
||||
"updated_at": "2026-05-22T20:13:39.169Z"
|
||||
},
|
||||
"fr": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "8dca6de87c88e04396f1139ac8b6328d188defaa5abc99eeb4026f53de772ba1",
|
||||
"updated_at": "2026-05-22T20:13:39.170Z"
|
||||
},
|
||||
"he": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "0308a2c8c5a6b6261474a76c160f3c0658c75f56a633a57bd7300b83ccb32c9d",
|
||||
"updated_at": "2026-05-22T20:13:39.170Z"
|
||||
},
|
||||
"hi": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "a3e902c3b1812a41ccb14c9f16d2cf2e42b8c005a5d557043e582d48eda024c4",
|
||||
"updated_at": "2026-05-22T20:13:39.170Z"
|
||||
},
|
||||
"gu": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "1bc2419db39c7c7960b065222a3e3990e9e53c4d3427ec02422e3e506aa53733",
|
||||
"updated_at": "2026-05-22T20:13:39.171Z"
|
||||
},
|
||||
"hu": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "ec30f54810f8b5b84e3ac8bf7bc8d05acc8616d71801b672ec02f0bc225cf607",
|
||||
"updated_at": "2026-05-22T20:13:39.171Z"
|
||||
},
|
||||
"id": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "aebd8fdad7ec4857aa1b958cda37f59681846525207d71d98d729775031afb94",
|
||||
"updated_at": "2026-05-22T20:13:39.172Z"
|
||||
},
|
||||
"in": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "ac70a1282877f8c4f792e9235f7a6fbce3cbec4a424ce4947074bb210fe12d67",
|
||||
"updated_at": "2026-05-22T20:13:39.172Z"
|
||||
},
|
||||
"it": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "260f10dd121441d08e15a8d511789d8f8f7a788995305ab5e8dc6d0c1a3db06e",
|
||||
"updated_at": "2026-05-22T20:13:39.173Z"
|
||||
},
|
||||
"ja": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "83871c13e99d13d7928409b2aef182f55fe36fbb2425f8fa4bd0df0466afed28",
|
||||
"updated_at": "2026-05-22T20:13:39.173Z"
|
||||
},
|
||||
"mr": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "0c35c5261dd3b6c6b729d14653f95cc112f3ed9829d2d41b61e5a960abc68556",
|
||||
"updated_at": "2026-05-22T20:13:39.173Z"
|
||||
},
|
||||
"ko": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "4ea4079b37d90e90e9a32d0da290e04e38bcd6426512b50ca7be7139354bc329",
|
||||
"updated_at": "2026-05-22T20:13:39.174Z"
|
||||
},
|
||||
"ms": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "0e8031cd987766a69afc7aaf7c3560a57736d37e7238ddccfb848d6fbeb3f212",
|
||||
"updated_at": "2026-05-22T20:13:39.174Z"
|
||||
},
|
||||
"nl": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "27e554c3a9b86db1f04539fcac18d3e75e6599d4de9f5ac0cf789a136b5737d4",
|
||||
"updated_at": "2026-05-22T20:13:39.174Z"
|
||||
},
|
||||
"phi": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "f955af44cc0e8e87a2a7b12313f0dfad9aae1a50d7855e74c8155df3601279ad",
|
||||
"updated_at": "2026-05-22T20:13:39.175Z"
|
||||
},
|
||||
"no": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "de4e3d940ae485c85da13db4471fcc68926ec53e660d92633f01293c5db0a04d",
|
||||
"updated_at": "2026-05-22T20:13:39.175Z"
|
||||
},
|
||||
"pl": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "de5be8961e5184431404d0021e31c3ef0857f7439fbd1c71ddc0c6828bed86bb",
|
||||
"updated_at": "2026-05-22T20:13:39.175Z"
|
||||
},
|
||||
"ro": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "19d98b22bc77c1870b749fad6e62a41c6ef53f3cad303d1c471fba4bf7012797",
|
||||
"updated_at": "2026-05-22T20:13:39.175Z"
|
||||
},
|
||||
"ru": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "25e0c70ec25bd24b073b9d693299c3c3d32d66a410569362719e9c9ecacd759d",
|
||||
"updated_at": "2026-05-22T20:13:39.176Z"
|
||||
},
|
||||
"pt": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "0d31647b21af967d8d4e9ecbcfc4901a66db377a6853d7b59296a2d73f0cab12",
|
||||
"updated_at": "2026-05-22T20:13:39.176Z"
|
||||
},
|
||||
"sk": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "9d5d0ce1c51da4959d1c306969bff0bf36d3a3492ccdbdbd82e4f4d951f32c8b",
|
||||
"updated_at": "2026-05-22T20:13:39.176Z"
|
||||
},
|
||||
"sw": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "11cd3dc7a605eebddf4a34c13de66c713d67adb32e4242962a65c71e5a034c7e",
|
||||
"updated_at": "2026-05-22T20:13:39.177Z"
|
||||
},
|
||||
"sv": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "93b679feb6415315ab96e08298217e66be84265a277e267ecefd25b2cef04026",
|
||||
"updated_at": "2026-05-22T20:13:39.177Z"
|
||||
},
|
||||
"ta": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "55f3df128513a1b4a8cc3f58eb84769814589d80e1c8b8f03ab0635750a76d2d",
|
||||
"updated_at": "2026-05-22T20:13:39.177Z"
|
||||
},
|
||||
"te": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "81019604dd1fb38dfda55c1b8cf5cf8243347be08221a5e1eb11e8c76e095fab",
|
||||
"updated_at": "2026-05-22T20:13:39.178Z"
|
||||
},
|
||||
"tr": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "b67efd07b179be016b388dff4b8979597c0a7b2db602629cef539dbec14913ab",
|
||||
"updated_at": "2026-05-22T20:13:39.178Z"
|
||||
},
|
||||
"th": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "a793a12dc0cba5e3641cd544f63ab23e9cfe1b80568a08770a81ffd890287eda",
|
||||
"updated_at": "2026-05-22T20:13:39.179Z"
|
||||
},
|
||||
"ur": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "6c03049aee6d85b6fd4a3bf9b89a6204e461d3f05e9cb767e36c80af796452df",
|
||||
"updated_at": "2026-05-22T20:13:39.179Z"
|
||||
},
|
||||
"vi": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "4d35c6898f98913a02551dcfbf4d3748b2461e2d8206d292f292acacf0fc7133",
|
||||
"updated_at": "2026-05-22T20:13:39.180Z"
|
||||
},
|
||||
"zh-CN": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "d3f574c244d157c1fe1b9fc86192d1565df2dc6343ac9ef63c0fd3f91d97f492",
|
||||
"updated_at": "2026-05-22T20:13:39.180Z"
|
||||
},
|
||||
"uk-UA": {
|
||||
"source_hash": "c808d01e42e9aaaede590048dc327347c9e814b152c6f45f54be6df64f4f19df",
|
||||
"target_hash": "ffe6357e38d56ba2595e8fb8e21db4ae91bf03d10fca50a2b722e0d0bb6c01d9",
|
||||
"updated_at": "2026-05-22T20:13:39.181Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docs/architecture/ARCHITECTURE.md": {
|
||||
"source_hash": "f9b4f17a1b0331fb5500768943438411ed88a38278381680caacc37c90bfb869",
|
||||
"locales": {
|
||||
"pt-BR": {
|
||||
"source_hash": "f9b4f17a1b0331fb5500768943438411ed88a38278381680caacc37c90bfb869",
|
||||
"target_hash": "e320c6172a88a0f3b698ba1a2e9e938424ece66625765395837bcf55cc29454e",
|
||||
"updated_at": "2026-05-22T20:13:39.183Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
.semgrep/rules/cli-no-sqlite.yaml
Normal file
31
.semgrep/rules/cli-no-sqlite.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
rules:
|
||||
- id: cli-no-sqlite-direct
|
||||
patterns:
|
||||
- pattern: new Database(...)
|
||||
paths:
|
||||
include:
|
||||
- "/bin/**"
|
||||
exclude:
|
||||
- "/bin/cli/sqlite.mjs"
|
||||
message: >
|
||||
Direct SQLite access in bin/ is banned. Use src/lib/db/* helpers or
|
||||
withRuntime() from bin/cli/runtime.mjs. See CLAUDE.md hard rule #5 and
|
||||
bin/cli/CONVENTIONS.md.
|
||||
languages: [js]
|
||||
severity: ERROR
|
||||
|
||||
- id: cli-no-raw-sql
|
||||
patterns:
|
||||
- pattern: $DB.prepare("INSERT INTO ...")
|
||||
- pattern: $DB.prepare("DELETE FROM ...")
|
||||
- pattern: $DB.prepare("UPDATE $TABLE SET ...")
|
||||
paths:
|
||||
include:
|
||||
- "/bin/**"
|
||||
exclude:
|
||||
- "/bin/cli/sqlite.mjs"
|
||||
message: >
|
||||
Raw SQL in bin/ is banned. Use src/lib/db/* helpers. See CLAUDE.md
|
||||
hard rule #5 and bin/cli/CONVENTIONS.md.
|
||||
languages: [js]
|
||||
severity: ERROR
|
||||
12
.source/browser.ts
Normal file
12
.source/browser.ts
Normal file
File diff suppressed because one or more lines are too long
75
.source/server.ts
Normal file
75
.source/server.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// @ts-nocheck
|
||||
import { default as __fd_glob_64 } from "../docs/security/meta.json?collection=docs"
|
||||
import { default as __fd_glob_63 } from "../docs/routing/meta.json?collection=docs"
|
||||
import { default as __fd_glob_62 } from "../docs/reference/openapi.yaml?collection=docs"
|
||||
import { default as __fd_glob_61 } from "../docs/reference/meta.json?collection=docs"
|
||||
import { default as __fd_glob_60 } from "../docs/ops/meta.json?collection=docs"
|
||||
import { default as __fd_glob_59 } from "../docs/guides/meta.json?collection=docs"
|
||||
import { default as __fd_glob_58 } from "../docs/frameworks/meta.json?collection=docs"
|
||||
import { default as __fd_glob_57 } from "../docs/compression/meta.json?collection=docs"
|
||||
import { default as __fd_glob_56 } from "../docs/architecture/meta.json?collection=docs"
|
||||
import { default as __fd_glob_55 } from "../docs/meta.json?collection=docs"
|
||||
import * as __fd_glob_54 from "../docs/security/STEALTH_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_53 from "../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"
|
||||
import * as __fd_glob_52 from "../docs/security/PUBLIC_CREDS.md?collection=docs"
|
||||
import * as __fd_glob_51 from "../docs/security/GUARDRAILS.md?collection=docs"
|
||||
import * as __fd_glob_50 from "../docs/security/ERROR_SANITIZATION.md?collection=docs"
|
||||
import * as __fd_glob_49 from "../docs/security/COMPLIANCE.md?collection=docs"
|
||||
import * as __fd_glob_48 from "../docs/security/CLI_TOKEN_AUTH.md?collection=docs"
|
||||
import * as __fd_glob_47 from "../docs/security/CLI_TOKEN.md?collection=docs"
|
||||
import * as __fd_glob_46 from "../docs/routing/REASONING_REPLAY.md?collection=docs"
|
||||
import * as __fd_glob_45 from "../docs/routing/AUTO-COMBO.md?collection=docs"
|
||||
import * as __fd_glob_44 from "../docs/reference/PROVIDER_REFERENCE.md?collection=docs"
|
||||
import * as __fd_glob_43 from "../docs/reference/FREE_TIERS.md?collection=docs"
|
||||
import * as __fd_glob_42 from "../docs/reference/ENVIRONMENT.md?collection=docs"
|
||||
import * as __fd_glob_41 from "../docs/reference/CLI-TOOLS.md?collection=docs"
|
||||
import * as __fd_glob_40 from "../docs/reference/API_REFERENCE.md?collection=docs"
|
||||
import * as __fd_glob_39 from "../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_38 from "../docs/ops/TUNNELS_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_37 from "../docs/ops/SQLITE_RUNTIME.md?collection=docs"
|
||||
import * as __fd_glob_36 from "../docs/ops/RELEASE_CHECKLIST.md?collection=docs"
|
||||
import * as __fd_glob_35 from "../docs/ops/PROXY_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_34 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_33 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"
|
||||
import * as __fd_glob_32 from "../docs/ops/COVERAGE_PLAN.md?collection=docs"
|
||||
import * as __fd_glob_31 from "../docs/guides/USER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_30 from "../docs/guides/UNINSTALL.md?collection=docs"
|
||||
import * as __fd_glob_29 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
|
||||
import * as __fd_glob_28 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_27 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_26 from "../docs/guides/PWA_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_25 from "../docs/guides/KIRO_SETUP.md?collection=docs"
|
||||
import * as __fd_glob_24 from "../docs/guides/I18N.md?collection=docs"
|
||||
import * as __fd_glob_23 from "../docs/guides/FEATURES.md?collection=docs"
|
||||
import * as __fd_glob_22 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_21 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_20 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
|
||||
import * as __fd_glob_19 from "../docs/frameworks/SKILLS.md?collection=docs"
|
||||
import * as __fd_glob_18 from "../docs/frameworks/OPENCODE.md?collection=docs"
|
||||
import * as __fd_glob_17 from "../docs/frameworks/MEMORY.md?collection=docs"
|
||||
import * as __fd_glob_16 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_15 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
|
||||
import * as __fd_glob_14 from "../docs/frameworks/EVALS.md?collection=docs"
|
||||
import * as __fd_glob_13 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
|
||||
import * as __fd_glob_12 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
|
||||
import * as __fd_glob_11 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_10 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_9 from "../docs/compression/RTK_COMPRESSION.md?collection=docs"
|
||||
import * as __fd_glob_8 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"
|
||||
import * as __fd_glob_7 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"
|
||||
import * as __fd_glob_6 from "../docs/compression/COMPRESSION_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_5 from "../docs/compression/COMPRESSION_ENGINES.md?collection=docs"
|
||||
import * as __fd_glob_4 from "../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_3 from "../docs/architecture/REPOSITORY_MAP.md?collection=docs"
|
||||
import * as __fd_glob_2 from "../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"
|
||||
import * as __fd_glob_1 from "../docs/architecture/AUTHZ_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_0 from "../docs/architecture/ARCHITECTURE.md?collection=docs"
|
||||
import { server } from 'fumadocs-mdx/runtime/server';
|
||||
import type * as Config from '../source.config';
|
||||
|
||||
const create = server<typeof Config, import("fumadocs-mdx/runtime/types").InternalTypeConfig & {
|
||||
DocData: {
|
||||
}
|
||||
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
||||
|
||||
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "compression/meta.json": __fd_glob_57, "frameworks/meta.json": __fd_glob_58, "guides/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "frameworks/A2A-SERVER.md": __fd_glob_10, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_11, "frameworks/CLOUD_AGENT.md": __fd_glob_12, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_13, "frameworks/EVALS.md": __fd_glob_14, "frameworks/GAMIFICATION.md": __fd_glob_15, "frameworks/MCP-SERVER.md": __fd_glob_16, "frameworks/MEMORY.md": __fd_glob_17, "frameworks/OPENCODE.md": __fd_glob_18, "frameworks/SKILLS.md": __fd_glob_19, "frameworks/WEBHOOKS.md": __fd_glob_20, "guides/DOCKER_GUIDE.md": __fd_glob_21, "guides/ELECTRON_GUIDE.md": __fd_glob_22, "guides/FEATURES.md": __fd_glob_23, "guides/I18N.md": __fd_glob_24, "guides/KIRO_SETUP.md": __fd_glob_25, "guides/PWA_GUIDE.md": __fd_glob_26, "guides/SETUP_GUIDE.md": __fd_glob_27, "guides/TERMUX_GUIDE.md": __fd_glob_28, "guides/TROUBLESHOOTING.md": __fd_glob_29, "guides/UNINSTALL.md": __fd_glob_30, "guides/USER_GUIDE.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, });
|
||||
@@ -4,7 +4,6 @@ var docs = defineDocs({
|
||||
dir: "docs",
|
||||
docs: {
|
||||
files: [
|
||||
"./getting-started/**/*.md",
|
||||
"./architecture/**/*.md",
|
||||
"./guides/**/*.md",
|
||||
"./reference/**/*.md",
|
||||
|
||||
31
.vscode/launch.json
vendored
Normal file
31
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Dev Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${env:HOME}/.nvm/versions/node/v22.22.2/bin/node",
|
||||
"program": "${workspaceFolder}/scripts/run-next.mjs",
|
||||
"args": ["dev"],
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
},
|
||||
{
|
||||
"name": "Debug Prod Server",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/scripts/run-next.mjs",
|
||||
"args": ["start"],
|
||||
"console": "integratedTerminal",
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
},
|
||||
{
|
||||
"name": "Attach to Running Server",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"skipFiles": ["<node_internals>/**", "node_modules/**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
@@ -36,18 +36,7 @@
|
||||
"**/node_modules/**": true,
|
||||
"**/.next/**": true,
|
||||
"**/coverage/**": true,
|
||||
"**/_tasks/**": true,
|
||||
"**/.git/objects/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/build/**": true,
|
||||
"**/out/**": true,
|
||||
"**/.cache/**": true,
|
||||
"**/.turbo/**": true,
|
||||
"**/OmniRoute-*/**": true,
|
||||
"**/*-merge-*/**": true,
|
||||
"**/*-worktree-*/**": true,
|
||||
"**/*-issues-*/**": true,
|
||||
"**/*-reorg*/**": true
|
||||
"**/_tasks/**": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/_references": true,
|
||||
@@ -56,14 +45,6 @@
|
||||
"**/node_modules": true,
|
||||
"**/.next": true,
|
||||
"**/coverage": true,
|
||||
"**/_tasks": true,
|
||||
"**/dist": true,
|
||||
"**/build": true,
|
||||
"**/out": true,
|
||||
"**/OmniRoute-*": true,
|
||||
"**/*-merge-*": true,
|
||||
"**/*-worktree-*": true,
|
||||
"**/*-issues-*": true,
|
||||
"**/*-reorg*": true
|
||||
"**/_tasks": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
# @omniroute/opencode-plugin
|
||||
|
||||
> **Recommended way to use OmniRoute with OpenCode.** Pulls a live model catalog from `/v1/models` (including `-low`/`-medium`/`-high`/`-thinking` variants as first-class IDs), aggregates combos via `/api/combos` using a least-common-denominator capability/limit join, sanitizes Gemini tool schemas in flight, and supports multiple side-by-side OmniRoute instances out of the box.
|
||||
|
||||
## Why this and not `@omniroute/opencode-provider`?
|
||||
|
||||
`@omniroute/opencode-provider` is the legacy config-generator package — it writes a frozen `provider.omniroute` block into `opencode.json` with a **hardcoded list of 8 models** ([`OMNIROUTE_DEFAULT_OPENCODE_MODELS`](https://github.com/diegosouzapw/OmniRoute/blob/main/%40omniroute/opencode-provider/src/index.ts#L48-L56)). It works on the CLI but in the **OpenCode Desktop / Web** builds (Tauri / Electron) the runtime re-runs the model picker and the static block surfaces only a few of those — and they drift behind the live OmniRoute catalog.
|
||||
|
||||
This plugin solves that by:
|
||||
|
||||
- Fetching `/v1/models` and `/api/combos` **at OpenCode startup, in Node.js** — no CORS, no WebView restrictions
|
||||
- Emitting the provider block **dynamically** in the plugin's `config`/`provider` hook — so `opencode.json` only needs the plugin entry, not a static `provider.omniroute`
|
||||
- Re-fetching on a configurable TTL (default 5 min), so new models / combo changes in the OmniRoute UI appear without restarting OpenCode
|
||||
- Computing `limit.context` for combos as `min(member.context_length)` from the live catalog (no more `null` values that cause 4K-token truncation)
|
||||
- **Auto-pickup of `interleaved` capability** for thinking models (merged via PR #3138)
|
||||
|
||||
**If you only have the legacy `opencode-provider` block in your `opencode.json`, replace it with a single plugin entry.** No other config changes required — the same `auth.json` API key works.
|
||||
First-class OpenCode plugin for the [OmniRoute AI Gateway](https://github.com/diegosouzapw/OmniRoute). Pulls a live model catalog from `/v1/models` (including `-low`/`-medium`/`-high`/`-thinking` variants as first-class IDs), aggregates combos via `/api/combos` using a least-common-denominator capability/limit join, sanitizes Gemini tool schemas in flight, and supports multiple side-by-side OmniRoute instances out of the box.
|
||||
|
||||
## Install
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts",
|
||||
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts",
|
||||
"prepublishOnly": "npm run clean && npm run build && npm test"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Structured logger for the OmniRoute plugin.
|
||||
*
|
||||
* Levels: error < warn < info < debug
|
||||
* Default: warn (matches current console.warn behavior)
|
||||
* Set via features.logLevel in plugin options.
|
||||
*/
|
||||
|
||||
export type LogLevel = "error" | "warn" | "info" | "debug";
|
||||
|
||||
const LEVEL_ORDER: Record<LogLevel, number> = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
debug: 3,
|
||||
};
|
||||
|
||||
const TAG = "[omniroute-plugin]";
|
||||
|
||||
function shouldLog(current: LogLevel, target: LogLevel): boolean {
|
||||
return LEVEL_ORDER[current] >= LEVEL_ORDER[target];
|
||||
}
|
||||
|
||||
let _level: LogLevel = "warn";
|
||||
|
||||
export function setLogLevel(level: LogLevel): void {
|
||||
_level = level;
|
||||
}
|
||||
|
||||
export function getLogLevel(): LogLevel {
|
||||
return _level;
|
||||
}
|
||||
|
||||
function fmt(level: LogLevel, msg: string, tag?: string): string {
|
||||
const prefix = tag ? `${TAG}${tag}` : TAG;
|
||||
return `${prefix} [${level.toUpperCase()}] ${msg}`;
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
error(msg: string, ...args: unknown[]): void {
|
||||
if (shouldLog(_level, "error")) console.error(fmt("error", msg), ...args);
|
||||
},
|
||||
warn(msg: string, ...args: unknown[]): void {
|
||||
if (shouldLog(_level, "warn")) console.warn(fmt("warn", msg), ...args);
|
||||
},
|
||||
info(msg: string, ...args: unknown[]): void {
|
||||
if (shouldLog(_level, "info")) console.warn(fmt("info", msg), ...args);
|
||||
},
|
||||
debug(msg: string, ...args: unknown[]): void {
|
||||
if (shouldLog(_level, "debug")) console.warn(fmt("debug", msg), ...args);
|
||||
},
|
||||
/** Always emit regardless of level (for critical init breadcrumbs). */
|
||||
always(msg: string, ...args: unknown[]): void {
|
||||
console.warn(TAG, msg, ...args);
|
||||
},
|
||||
|
||||
// ── Tagged child loggers ──────────────────────────────────────────────
|
||||
child(tag: string) {
|
||||
return {
|
||||
error: (msg: string, ...args: unknown[]) =>
|
||||
shouldLog(_level, "error") &&
|
||||
console.error(fmt("error", msg, tag), ...args),
|
||||
warn: (msg: string, ...args: unknown[]) =>
|
||||
shouldLog(_level, "warn") &&
|
||||
console.warn(fmt("warn", msg, tag), ...args),
|
||||
info: (msg: string, ...args: unknown[]) =>
|
||||
shouldLog(_level, "info") &&
|
||||
console.warn(fmt("info", msg, tag), ...args),
|
||||
debug: (msg: string, ...args: unknown[]) =>
|
||||
shouldLog(_level, "debug") &&
|
||||
console.warn(fmt("debug", msg, tag), ...args),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,301 +0,0 @@
|
||||
/**
|
||||
* Universal model naming template for the OmniRoute plugin.
|
||||
*
|
||||
* Naming pipeline:
|
||||
* [tag] <provider-label><separator><display-name><suffix>
|
||||
*
|
||||
* [Free] <provider> - <name> · <budget> ← free model
|
||||
* Auto: <variant> (<N>p) ← auto combo
|
||||
* Combo: <name> ← DB combo
|
||||
* <provider> - <name> ← regular model
|
||||
*/
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────
|
||||
|
||||
/** Separator between provider label and model display name. */
|
||||
export const PROVIDER_TAG_SEPARATOR = " - ";
|
||||
|
||||
/** Threshold beyond which providerDisplayName is abbreviated. */
|
||||
const PROVIDER_LABEL_MAX_CHARS = 12;
|
||||
|
||||
/** Aliases longer than this get title-case instead of UPPER. */
|
||||
const ALIAS_UPPER_MAX_CHARS = 5;
|
||||
|
||||
// ── Auto Combo Types ─────────────────────────────────────────────────────
|
||||
|
||||
export type AutoVariant =
|
||||
| "coding"
|
||||
| "fast"
|
||||
| "cheap"
|
||||
| "offline"
|
||||
| "smart"
|
||||
| "lkgp";
|
||||
|
||||
export const AUTO_VARIANTS: AutoVariant[] = [
|
||||
"coding",
|
||||
"fast",
|
||||
"cheap",
|
||||
"offline",
|
||||
"smart",
|
||||
"lkgp",
|
||||
];
|
||||
|
||||
export const AUTO_VARIANT_DESCRIPTIONS: Record<
|
||||
AutoVariant | "default",
|
||||
string
|
||||
> = {
|
||||
default: "Best provider via scoring",
|
||||
coding: "Quality-first for code tasks",
|
||||
fast: "Latency-optimized routing",
|
||||
cheap: "Cost-optimized routing",
|
||||
offline: "Offline-friendly providers",
|
||||
smart: "Quality-first with exploration",
|
||||
lkgp: "Last-Known-Good-Provider routing",
|
||||
};
|
||||
|
||||
// ── Free Model Types ─────────────────────────────────────────────────────
|
||||
|
||||
export type FreeModelFreeType =
|
||||
| "recurring-daily"
|
||||
| "recurring-monthly"
|
||||
| "recurring-credit"
|
||||
| "one-time-initial"
|
||||
| "keyless"
|
||||
| "discontinued";
|
||||
|
||||
// ── Provider Label ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Title-case a long, lowercase-looking alias.
|
||||
* `antigravity` → `Antigravity`
|
||||
*/
|
||||
function titleCaseAlias(alias: string): string {
|
||||
if (alias.length === 0) return alias;
|
||||
return alias.charAt(0).toUpperCase() + alias.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the short label for an upstream provider.
|
||||
*
|
||||
* Rules:
|
||||
* 1. Trim `providerDisplayName`. If ≤12 chars → use verbatim.
|
||||
* 2. Alias ≤5 chars → UPPER(alias). Alias >5 → titleCase.
|
||||
* 3. Neither → undefined.
|
||||
*/
|
||||
export function shortProviderLabel(
|
||||
enrichment:
|
||||
| { providerDisplayName?: string; providerAlias?: string }
|
||||
| undefined,
|
||||
): string | undefined {
|
||||
if (!enrichment) return undefined;
|
||||
const raw =
|
||||
typeof enrichment.providerDisplayName === "string"
|
||||
? enrichment.providerDisplayName.trim()
|
||||
: "";
|
||||
if (raw.length > 0 && raw.length <= PROVIDER_LABEL_MAX_CHARS) return raw;
|
||||
const alias =
|
||||
typeof enrichment.providerAlias === "string"
|
||||
? enrichment.providerAlias.trim()
|
||||
: "";
|
||||
if (alias.length > 0) {
|
||||
return alias.length <= ALIAS_UPPER_MAX_CHARS
|
||||
? alias.toUpperCase()
|
||||
: titleCaseAlias(alias);
|
||||
}
|
||||
// Long displayName with no alias to fall back on: keep the long label
|
||||
// rather than dropping the provider prefix entirely.
|
||||
return raw.length > 0 ? raw : undefined;
|
||||
}
|
||||
|
||||
// ── Free Label ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Normalise display name so free-tier models get a consistent `[Free] ` prefix.
|
||||
*
|
||||
* "GPT-4.1 (Free)" → "[Free] GPT-4.1"
|
||||
* "DeepSeek V4 Flash Free" → "[Free] DeepSeek V4 Flash"
|
||||
* "Claude Opus 4.7" → "Claude Opus 4.7" (unchanged)
|
||||
*/
|
||||
export function normaliseFreeLabel(name: string): string {
|
||||
// Bounded whitespace quantifiers ({0,8}/{1,8}) avoid the polynomial-ReDoS
|
||||
// backtracking that unbounded \s* before an anchored \s*$ would allow on
|
||||
// attacker-influenced display names. 8 covers any realistic label spacing.
|
||||
const cleaned = name
|
||||
.replace(/\s{0,8}\(free\)\s{0,8}$/i, "")
|
||||
.replace(/[\s-]{1,8}free\s{0,8}$/i, "")
|
||||
.trim();
|
||||
const wasFree = cleaned.length < name.trim().length;
|
||||
if (!wasFree) return name;
|
||||
return `[Free] ${cleaned}`;
|
||||
}
|
||||
|
||||
// ── Free Budget Formatting ────────────────────────────────────────────────
|
||||
|
||||
function fmtTokens(n: number): string {
|
||||
if (n >= 1e9) return (n / 1e9).toFixed(1).replace(/\.0$/, "") + "B";
|
||||
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
||||
if (n >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, "") + "K";
|
||||
return String(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a free model budget into a short human-readable suffix.
|
||||
*
|
||||
* recurring-daily → "25M tokens/day"
|
||||
* recurring-monthly → "25M tokens/month"
|
||||
* recurring-credit → "10M credits"
|
||||
* one-time-initial → "1M credits (one-time)"
|
||||
* keyless → "(keyless)"
|
||||
* discontinued → "(discontinued)"
|
||||
*/
|
||||
export function formatFreeBudget(params: {
|
||||
freeType: FreeModelFreeType;
|
||||
monthlyTokens?: number;
|
||||
creditTokens?: number;
|
||||
}): string {
|
||||
const { freeType, monthlyTokens = 0, creditTokens = 0 } = params;
|
||||
|
||||
switch (freeType) {
|
||||
case "recurring-daily":
|
||||
return `${fmtTokens(monthlyTokens)} tokens/day`;
|
||||
case "recurring-monthly":
|
||||
return `${fmtTokens(monthlyTokens)} tokens/month`;
|
||||
case "recurring-credit":
|
||||
return `${fmtTokens(creditTokens)} credits`;
|
||||
case "one-time-initial":
|
||||
return `${fmtTokens(creditTokens)} credits (one-time)`;
|
||||
case "keyless":
|
||||
return "(keyless)";
|
||||
case "discontinued":
|
||||
return "(discontinued)";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Auto Combo Naming ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Format auto combo display name.
|
||||
*
|
||||
* "Auto: Coding (4p)"
|
||||
* "Auto: Default (6p)"
|
||||
* "Auto" (no candidate count when unknown)
|
||||
*/
|
||||
export function formatAutoComboName(
|
||||
variant: AutoVariant | undefined,
|
||||
candidateCount?: number,
|
||||
): string {
|
||||
const label = variant
|
||||
? variant.charAt(0).toUpperCase() + variant.slice(1)
|
||||
: "Default";
|
||||
const count =
|
||||
typeof candidateCount === "number" && candidateCount > 0
|
||||
? ` (${candidateCount}p)`
|
||||
: "";
|
||||
return `Auto: ${label}${count}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the model ID for an auto combo entry.
|
||||
* "auto/coding", "auto/fast", "auto" (default).
|
||||
*/
|
||||
export function autoComboModelId(variant: AutoVariant | undefined): string {
|
||||
return variant ? `auto/${variant}` : "auto";
|
||||
}
|
||||
|
||||
// ── Universal Display Name Builder ────────────────────────────────────────
|
||||
|
||||
export interface ModelDisplayNameParams {
|
||||
/** Raw model ID (e.g. "cc/claude-sonnet-4-6"). */
|
||||
rawId: string;
|
||||
/** Enrichment display name (e.g. "Claude Sonnet 4.6"). */
|
||||
enrichmentName?: string;
|
||||
/** Provider tag enrichment. */
|
||||
providerAlias?: string;
|
||||
/** Human-readable upstream provider label. */
|
||||
providerDisplayName?: string;
|
||||
/** Whether model is free tier. */
|
||||
isFree?: boolean;
|
||||
/** Free model budget info. */
|
||||
freeType?: FreeModelFreeType;
|
||||
/** Monthly token budget (for recurring free models). */
|
||||
monthlyTokens?: number;
|
||||
/** Credit token budget (for credit-based free models). */
|
||||
creditTokens?: number;
|
||||
/** Whether this is a combo entry (skip provider tag). */
|
||||
isCombo?: boolean;
|
||||
/** Whether this is an auto combo entry. */
|
||||
isAutoCombo?: boolean;
|
||||
/** Auto combo variant. */
|
||||
autoVariant?: AutoVariant;
|
||||
/** Auto combo candidate count. */
|
||||
autoCandidateCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the final display name following the universal template.
|
||||
*
|
||||
* Priority:
|
||||
* 1. Auto combo → "Auto: <variant> (<N>p)"
|
||||
* 2. DB combo → "Combo: <name>"
|
||||
* 3. Free + enrichment + provider tag → "[Free] <label> - <name> · <budget>"
|
||||
* 4. Free + enrichment → "[Free] <name> · <budget>"
|
||||
* 5. Free + raw → "[Free] <rawId> · <budget>"
|
||||
* 6. Enrichment + provider tag → "<label> - <name>"
|
||||
* 7. Enrichment only → "<name>"
|
||||
* 8. Raw fallback → normaliseFreeLabel(rawId)
|
||||
*/
|
||||
export function buildModelDisplayName(params: ModelDisplayNameParams): string {
|
||||
// Auto combos
|
||||
if (params.isAutoCombo) {
|
||||
return formatAutoComboName(params.autoVariant, params.autoCandidateCount);
|
||||
}
|
||||
|
||||
// Determine base name — strip any existing free suffix first
|
||||
const rawBase =
|
||||
params.enrichmentName && params.enrichmentName.trim().length > 0
|
||||
? params.enrichmentName
|
||||
: params.rawId;
|
||||
const cleanedBase = rawBase
|
||||
.replace(/\s*\(free\)\s*$/i, "")
|
||||
.replace(/[\s-]+free\s*$/i, "")
|
||||
.trim();
|
||||
const wasFree = cleanedBase.length < rawBase.trim().length;
|
||||
const isFree = !!params.isFree || wasFree;
|
||||
|
||||
let baseName = cleanedBase;
|
||||
|
||||
// Provider tag (skip for combos)
|
||||
if (!params.isCombo) {
|
||||
const label = shortProviderLabel({
|
||||
providerDisplayName: params.providerDisplayName,
|
||||
providerAlias: params.providerAlias,
|
||||
});
|
||||
if (label) {
|
||||
const prefix = `${label}${PROVIDER_TAG_SEPARATOR}`;
|
||||
if (!baseName.startsWith(prefix)) {
|
||||
baseName = `${prefix}${baseName}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend [Free] if applicable (AFTER provider tag for correct ordering)
|
||||
if (isFree) {
|
||||
baseName = `[Free] ${baseName}`;
|
||||
}
|
||||
|
||||
// Free budget suffix
|
||||
if (isFree && params.freeType) {
|
||||
const budget = formatFreeBudget({
|
||||
freeType: params.freeType,
|
||||
monthlyTokens: params.monthlyTokens,
|
||||
creditTokens: params.creditTokens,
|
||||
});
|
||||
if (budget) {
|
||||
baseName = `${baseName} · ${budget}`;
|
||||
}
|
||||
}
|
||||
|
||||
return baseName;
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
/**
|
||||
* Tests for the 3 mrmm-fork features backported to @omniroute/opencode-plugin:
|
||||
*
|
||||
* 1. `normaliseFreeLabel` — free-tier model display names get a consistent
|
||||
* `[Free] ` prefix instead of trailing "(Free)" or ad-hoc "free" words.
|
||||
*
|
||||
* 2. `resolveApiBlock` — per-provider-prefix API format routing. Anthropic
|
||||
* prefixes (`cc/`, `claude/`, `anthropic/`, `kiro/`, `kr/`) get the
|
||||
* Anthropic SDK block; everything else gets OpenAI-compat.
|
||||
*
|
||||
* 3. `debugLog` — JSONL request/response capture, gated by
|
||||
* `features.debugLog` and togglable at runtime via
|
||||
* `debugLogEnabled/SetEnabled`.
|
||||
*/
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
normaliseFreeLabel,
|
||||
resolveApiBlock,
|
||||
DEFAULT_ANTHROPIC_PREFIXES,
|
||||
ensureV1Suffix,
|
||||
debugLogEnabled,
|
||||
debugLogSetEnabled,
|
||||
debugLogClear,
|
||||
debugLogRead,
|
||||
debugLogAppend,
|
||||
createDebugLoggingFetch,
|
||||
DebugLogEntry,
|
||||
} from "../src/index.js";
|
||||
|
||||
// ── 1. normaliseFreeLabel ────────────────────────────────────────────────────
|
||||
|
||||
test("normaliseFreeLabel: '(Free)' suffix becomes [Free] prefix", () => {
|
||||
assert.equal(normaliseFreeLabel("GPT-4.1 (Free)"), "[Free] GPT-4.1");
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: trailing ' Free' word becomes [Free] prefix", () => {
|
||||
assert.equal(
|
||||
normaliseFreeLabel("DeepSeek V4 Flash Free"),
|
||||
"[Free] DeepSeek V4 Flash"
|
||||
);
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: trailing '-free' (hyphen) becomes [Free] prefix", () => {
|
||||
assert.equal(normaliseFreeLabel("Llama 4 Scout-free"), "[Free] Llama 4 Scout");
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: case-insensitive (FREE, Free, free all match)", () => {
|
||||
assert.equal(normaliseFreeLabel("Model A FREE"), "[Free] Model A");
|
||||
assert.equal(normaliseFreeLabel("Model A free"), "[Free] Model A");
|
||||
assert.equal(normaliseFreeLabel("Model A Free"), "[Free] Model A");
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: names without 'free' pass through unchanged", () => {
|
||||
assert.equal(normaliseFreeLabel("Claude 4.7 Opus"), "Claude 4.7 Opus");
|
||||
assert.equal(normaliseFreeLabel("GPT-5"), "GPT-5");
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: 'free' in the middle of a name is NOT rewritten", () => {
|
||||
// Only trailing/standalone "free" markers count; embedded "freedom" stays
|
||||
assert.equal(
|
||||
normaliseFreeLabel("Freedom Model"),
|
||||
"Freedom Model"
|
||||
);
|
||||
});
|
||||
|
||||
test("normaliseFreeLabel: empty / whitespace-only inputs are handled", () => {
|
||||
// Empty input returns empty; pure whitespace input passes through (no Free marker)
|
||||
assert.equal(normaliseFreeLabel(""), "");
|
||||
assert.equal(normaliseFreeLabel(" "), " ");
|
||||
});
|
||||
|
||||
// ── 2. resolveApiBlock ───────────────────────────────────────────────────────
|
||||
|
||||
test("resolveApiBlock: cc/* models get the Anthropic SDK block (no /v1)", () => {
|
||||
const block = resolveApiBlock("cc/claude-opus-4-7", "https://api.example.com");
|
||||
assert.equal(block.id, "anthropic");
|
||||
assert.equal(block.npm, "@ai-sdk/anthropic");
|
||||
assert.equal(block.url, "https://api.example.com"); // NO /v1 suffix
|
||||
});
|
||||
|
||||
test("resolveApiBlock: claude/*, anthropic/*, kiro/*, kr/* all route to Anthropic", () => {
|
||||
for (const id of [
|
||||
"claude/claude-opus-4-7",
|
||||
"anthropic/claude-sonnet-4",
|
||||
"kiro/claude-sonnet-4-5",
|
||||
"kr/claude-opus-4-6",
|
||||
]) {
|
||||
const block = resolveApiBlock(id, "https://api.example.com");
|
||||
assert.equal(block.id, "anthropic", `${id} should route to Anthropic`);
|
||||
assert.equal(block.npm, "@ai-sdk/anthropic");
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveApiBlock: non-Anthropic models get OpenAI-compat with /v1", () => {
|
||||
const block = resolveApiBlock("gpt-4o", "https://api.example.com");
|
||||
assert.equal(block.id, "openai-compatible");
|
||||
assert.equal(block.npm, "@ai-sdk/openai-compatible");
|
||||
assert.equal(block.url, "https://api.example.com/v1");
|
||||
});
|
||||
|
||||
test("resolveApiBlock: user can override anthropicPrefixes to add custom prefixes", () => {
|
||||
const block = resolveApiBlock("myproxy/claude-opus", "https://api.example.com", {
|
||||
anthropicPrefixes: ["myproxy"],
|
||||
});
|
||||
assert.equal(block.id, "anthropic");
|
||||
assert.equal(block.npm, "@ai-sdk/anthropic");
|
||||
});
|
||||
|
||||
test("resolveApiBlock: empty anthropicPrefixes forces OpenAI-compat for everything", () => {
|
||||
const block = resolveApiBlock("cc/claude-opus", "https://api.example.com", {
|
||||
anthropicPrefixes: [],
|
||||
});
|
||||
assert.equal(block.id, "openai-compatible");
|
||||
});
|
||||
|
||||
test("resolveApiBlock: baseURL that already ends in /v1 is not double-suffixed (OpenAI path)", () => {
|
||||
const block = resolveApiBlock("gpt-4o", "https://api.example.com/v1");
|
||||
assert.equal(block.url, "https://api.example.com/v1"); // idempotent
|
||||
});
|
||||
|
||||
test("resolveApiBlock: model id without '/' uses the id as prefix", () => {
|
||||
const block = resolveApiBlock("claude-opus-4-7", "https://api.example.com");
|
||||
// The whole id is the prefix, which doesn't match "cc"/"claude" etc.
|
||||
// So it falls through to OpenAI-compat.
|
||||
assert.equal(block.id, "openai-compatible");
|
||||
});
|
||||
|
||||
test("DEFAULT_ANTHROPIC_PREFIXES: contains the canonical Anthropic aliases", () => {
|
||||
assert.deepEqual(DEFAULT_ANTHROPIC_PREFIXES, [
|
||||
"cc",
|
||||
"claude",
|
||||
"anthropic",
|
||||
"kiro",
|
||||
"kr",
|
||||
]);
|
||||
});
|
||||
|
||||
test("ensureV1Suffix: idempotent for URLs that already end in /v1", () => {
|
||||
assert.equal(ensureV1Suffix("https://api.example.com/v1"), "https://api.example.com/v1");
|
||||
assert.equal(
|
||||
ensureV1Suffix("https://api.example.com/v1/"),
|
||||
"https://api.example.com/v1" // trailing slash is stripped
|
||||
);
|
||||
});
|
||||
|
||||
test("ensureV1Suffix: appends /v1 when missing", () => {
|
||||
assert.equal(ensureV1Suffix("https://api.example.com"), "https://api.example.com/v1");
|
||||
assert.equal(ensureV1Suffix("https://api.example.com/"), "https://api.example.com/v1");
|
||||
});
|
||||
|
||||
// ── 3. debugLog ──────────────────────────────────────────────────────────────
|
||||
|
||||
test("debugLog: default state is disabled", () => {
|
||||
debugLogClear("test-provider-disabled-default");
|
||||
assert.equal(debugLogEnabled("test-provider-disabled-default"), false);
|
||||
});
|
||||
|
||||
test("debugLogSetEnabled + debugLogEnabled: roundtrip", () => {
|
||||
debugLogSetEnabled("test-provider-toggle", true);
|
||||
assert.equal(debugLogEnabled("test-provider-toggle"), true);
|
||||
debugLogSetEnabled("test-provider-toggle", false);
|
||||
assert.equal(debugLogEnabled("test-provider-toggle"), false);
|
||||
});
|
||||
|
||||
test("debugLogAppend + debugLogRead: roundtrip preserves entry shape", () => {
|
||||
const providerId = "test-provider-readroundtrip";
|
||||
debugLogClear(providerId);
|
||||
const entry: DebugLogEntry = {
|
||||
reqId: "req-1",
|
||||
providerId,
|
||||
ts: 1700000000000,
|
||||
url: "https://api.example.com/v1/chat",
|
||||
method: "POST",
|
||||
reqHeaders: { "content-type": "application/json" },
|
||||
reqBody: { model: "gpt-4o", messages: [] },
|
||||
resStatus: 200,
|
||||
resHeaders: { "content-type": "application/json" },
|
||||
resBody: { choices: [] },
|
||||
durationMs: 42,
|
||||
};
|
||||
debugLogAppend(entry);
|
||||
const read = debugLogRead(providerId, 10);
|
||||
assert.equal(read.length, 1);
|
||||
assert.deepEqual(read[0], entry);
|
||||
});
|
||||
|
||||
test("createDebugLoggingFetch: passes through when disabled", async () => {
|
||||
const providerId = "test-provider-passthrough";
|
||||
debugLogClear(providerId);
|
||||
debugLogSetEnabled(providerId, false);
|
||||
const calls: unknown[] = [];
|
||||
const inner: typeof fetch = async (input) => {
|
||||
calls.push(input);
|
||||
return new Response("ok", { status: 200 });
|
||||
};
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, false);
|
||||
const res = await wrapped("https://api.example.com/v1/chat");
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(calls.length, 1);
|
||||
// No log entry should be written when disabled
|
||||
assert.equal(debugLogRead(providerId).length, 0);
|
||||
});
|
||||
|
||||
test("createDebugLoggingFetch: captures request/response when enabled", async () => {
|
||||
const providerId = "test-provider-captures";
|
||||
debugLogClear(providerId);
|
||||
const inner: typeof fetch = async () =>
|
||||
new Response(JSON.stringify({ ok: true }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, true);
|
||||
const res = await wrapped("https://api.example.com/v1/chat", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ model: "gpt-4o" }),
|
||||
});
|
||||
assert.equal(res.status, 200);
|
||||
const entries = debugLogRead(providerId);
|
||||
assert.equal(entries.length, 1);
|
||||
assert.equal(entries[0].method, "POST");
|
||||
assert.equal(entries[0].resStatus, 200);
|
||||
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
|
||||
assert.deepEqual(entries[0].reqBody, { model: "gpt-4o" });
|
||||
});
|
||||
|
||||
test("createDebugLoggingFetch: records error without crashing the wrapped fetch", async () => {
|
||||
const providerId = "test-provider-error";
|
||||
debugLogClear(providerId);
|
||||
const inner: typeof fetch = async () => {
|
||||
throw new Error("network down");
|
||||
};
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, true);
|
||||
await assert.rejects(wrapped("https://api.example.com/v1/chat"), /network down/);
|
||||
const entries = debugLogRead(providerId);
|
||||
assert.equal(entries.length, 1);
|
||||
assert.equal(entries[0].resStatus, null);
|
||||
assert.equal(entries[0].error, "network down");
|
||||
});
|
||||
|
||||
// ── Regression tests for the 3 HIGH-priority bot review fixes ───────────────
|
||||
|
||||
test("createDebugLoggingFetch: URL instance input is captured (not 'undefined')", async () => {
|
||||
const providerId = "test-provider-url-input";
|
||||
debugLogClear(providerId);
|
||||
const inner: typeof fetch = async () =>
|
||||
new Response("ok", { status: 200 });
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, true);
|
||||
await wrapped(new URL("https://api.example.com/v1/chat"));
|
||||
const entries = debugLogRead(providerId);
|
||||
assert.equal(entries.length, 1);
|
||||
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
|
||||
assert.notEqual(entries[0].url, undefined);
|
||||
});
|
||||
|
||||
test("createDebugLoggingFetch: Request object input captures URL and headers", async () => {
|
||||
const providerId = "test-provider-request-input";
|
||||
debugLogClear(providerId);
|
||||
const inner: typeof fetch = async () =>
|
||||
new Response("ok", { status: 200 });
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, true);
|
||||
const req = new Request("https://api.example.com/v1/chat", {
|
||||
method: "POST",
|
||||
headers: { "x-test": "yes" },
|
||||
});
|
||||
await wrapped(req);
|
||||
const entries = debugLogRead(providerId);
|
||||
assert.equal(entries.length, 1);
|
||||
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
|
||||
assert.equal(entries[0].reqHeaders["x-test"], "yes");
|
||||
});
|
||||
|
||||
test("createDebugLoggingFetch: SSE response is NOT buffered (resBody is the stream marker)", async () => {
|
||||
const providerId = "test-provider-sse";
|
||||
debugLogClear(providerId);
|
||||
const inner: typeof fetch = async () =>
|
||||
new Response("data: hello\n\n", {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
});
|
||||
const wrapped = createDebugLoggingFetch(inner, providerId, true);
|
||||
const res = await wrapped("https://api.example.com/v1/stream");
|
||||
// The response body must remain readable downstream
|
||||
const txt = await res.text();
|
||||
assert.equal(txt, "data: hello\n\n");
|
||||
const entries = debugLogRead(providerId);
|
||||
assert.equal(entries.length, 1);
|
||||
assert.equal(entries[0].resBody, "[stream]", "SSE responses must not be buffered");
|
||||
});
|
||||
@@ -100,7 +100,7 @@ Exported for completeness. Strips trailing `/`, deduplicates a trailing `/v1`, a
|
||||
- `OMNIROUTE_PROVIDER_KEY` — `"omniroute"` (the key used under `provider.*`).
|
||||
- `OMNIROUTE_PROVIDER_NPM` — `"@ai-sdk/openai-compatible"` (the runtime delegate).
|
||||
- `OPENCODE_CONFIG_SCHEMA` — `"https://opencode.ai/config.json"`.
|
||||
- `OMNIROUTE_DEFAULT_OPENCODE_MODELS` — readonly list of default model ids.
|
||||
- `OMNIROUTE_DEFAULT_OPENCODE_MODELS` — readonly list of 4 default model ids.
|
||||
|
||||
## Custom model catalog
|
||||
|
||||
@@ -110,10 +110,10 @@ import { createOmniRouteProvider } from "@omniroute/opencode-provider";
|
||||
createOmniRouteProvider({
|
||||
baseURL: "http://localhost:20128",
|
||||
apiKey: "sk_omniroute",
|
||||
models: ["auto", "claude-opus-4-8", "gpt-5.5"],
|
||||
models: ["auto", "claude-opus-4-7", "gpt-5.5"],
|
||||
modelLabels: {
|
||||
auto: "Auto-Combo (recommended)",
|
||||
"claude-opus-4-8": "Claude Opus 4.8",
|
||||
"claude-opus-4-7": "Claude Opus 4.7",
|
||||
"gpt-5.5": "GPT-5.5",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -46,7 +46,6 @@ export const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json" as const
|
||||
* with Claude Code passthrough models (`cc/` prefix).
|
||||
*/
|
||||
export const OMNIROUTE_DEFAULT_OPENCODE_MODELS = [
|
||||
"cc/claude-opus-4-8",
|
||||
"cc/claude-opus-4-7",
|
||||
"cc/claude-sonnet-4-6",
|
||||
"cc/claude-haiku-4-5-20251001",
|
||||
@@ -84,8 +83,7 @@ export interface ModelCapabilities {
|
||||
* Matches the context lengths used by OmniRoute's provider registry.
|
||||
*/
|
||||
export const OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS: Record<string, number> = {
|
||||
"cc/claude-opus-4-8": 1_000_000,
|
||||
"cc/claude-opus-4-7": 1_000_000,
|
||||
"cc/claude-opus-4-7": 200_000,
|
||||
"cc/claude-sonnet-4-6": 200_000,
|
||||
"cc/claude-haiku-4-5-20251001": 200_000,
|
||||
"claude-opus-4-5-thinking": 200_000,
|
||||
@@ -102,7 +100,6 @@ export const OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS: Record<string, number> = {
|
||||
* model via `OmniRouteProviderOptions.modelCapabilities`.
|
||||
*/
|
||||
export const OMNIROUTE_DEFAULT_MODEL_CAPABILITIES: Record<string, ModelCapabilities> = {
|
||||
"cc/claude-opus-4-8": { attachment: true, reasoning: true, temperature: true, tool_call: true },
|
||||
"cc/claude-opus-4-7": { attachment: true, reasoning: true, temperature: true, tool_call: true },
|
||||
"cc/claude-sonnet-4-6": { attachment: true, reasoning: true, temperature: true, tool_call: true },
|
||||
"cc/claude-haiku-4-5-20251001": { attachment: true, temperature: true, tool_call: true },
|
||||
@@ -129,8 +126,8 @@ export interface OmniRouteProviderOptions {
|
||||
apiKey: string;
|
||||
/** Override the display name shown in OpenCode. Default: `"OmniRoute"`. */
|
||||
displayName?: string;
|
||||
/** Override the model catalog. Accepts model ids (strings) or live model entries from `fetchLiveModels`. When entries carry a `contextLength`, it is used directly — no hardcoded map needed. */
|
||||
models?: readonly (string | { id: string; contextLength?: number })[];
|
||||
/** Override the model catalog. Defaults to `OMNIROUTE_DEFAULT_OPENCODE_MODELS`. */
|
||||
models?: readonly string[];
|
||||
/** Optional human-readable labels keyed by model id. Overridden by `modelCapabilities[id].label`. */
|
||||
modelLabels?: Record<string, string>;
|
||||
/**
|
||||
@@ -139,12 +136,6 @@ export interface OmniRouteProviderOptions {
|
||||
* for custom ids the override is used verbatim.
|
||||
*/
|
||||
modelCapabilities?: Record<string, ModelCapabilities>;
|
||||
/**
|
||||
* Optional per-model context-length overrides (tokens). Takes precedence
|
||||
* over the static `OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS` map but is
|
||||
* superseded by `contextLength` on live model entries passed via `models`.
|
||||
*/
|
||||
modelContextLengths?: Record<string, string | number>;
|
||||
/**
|
||||
* Primary model for OpenCode (top-level `model` key).
|
||||
* Emitted as `"omniroute/<id>"`. When omitted the key is not written.
|
||||
@@ -254,12 +245,7 @@ export function createOmniRouteProvider(options: OmniRouteProviderOptions): Open
|
||||
const models: Record<string, OpenCodeModelEntry> = {};
|
||||
const seen = new Set<string>();
|
||||
for (const raw of modelList) {
|
||||
const id =
|
||||
typeof raw === "object" && raw !== null && "id" in raw && typeof (raw as any).id === "string"
|
||||
? (raw as { id: string }).id.trim()
|
||||
: typeof raw === "string"
|
||||
? raw.trim()
|
||||
: "";
|
||||
const id = typeof raw === "string" ? raw.trim() : "";
|
||||
if (!id || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
const defaults = OMNIROUTE_DEFAULT_MODEL_CAPABILITIES[id] ?? {};
|
||||
@@ -277,18 +263,10 @@ export function createOmniRouteProvider(options: OmniRouteProviderOptions): Open
|
||||
if (typeof merged.temperature === "boolean") entry.temperature = merged.temperature;
|
||||
if (typeof merged.tool_call === "boolean") entry.tool_call = merged.tool_call;
|
||||
|
||||
// Context window: live model entry (from API catalog) > modelContextLengths > static defaults
|
||||
const liveContext =
|
||||
typeof raw === "object" && raw !== null
|
||||
? (raw as { contextLength?: number }).contextLength
|
||||
: undefined;
|
||||
const rawContextLength =
|
||||
liveContext ??
|
||||
options.modelContextLengths?.[id] ??
|
||||
OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS[id];
|
||||
const contextLength =
|
||||
typeof rawContextLength === "string" ? parseInt(rawContextLength, 10) : rawContextLength;
|
||||
if (typeof contextLength === "number" && !isNaN(contextLength) && contextLength > 0) {
|
||||
// Include context window limit when known — OpenCode reads this to
|
||||
// determine usable context length for compaction & overflow detection.
|
||||
const contextLength = OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS[id];
|
||||
if (typeof contextLength === "number" && contextLength > 0) {
|
||||
entry.limit = { context: contextLength };
|
||||
}
|
||||
|
||||
@@ -527,7 +505,7 @@ export interface OmniRouteLiveModel {
|
||||
* const config = buildOmniRouteOpenCodeConfig({
|
||||
* baseURL: "http://localhost:20128",
|
||||
* apiKey: "sk_omniroute",
|
||||
* models, // OmniRouteLiveModel[] — contextLength auto-extracted
|
||||
* models: models.map((m) => m.id),
|
||||
* modelLabels: Object.fromEntries(models.map((m) => [m.id, m.name])),
|
||||
* });
|
||||
* ```
|
||||
|
||||
@@ -388,7 +388,6 @@ test("createOmniRouteComboConfig includes optional fields when supplied", () =>
|
||||
|
||||
test("OMNIROUTE_DEFAULT_OPENCODE_MODELS includes cc/ prefixed models", () => {
|
||||
const defaults = [...OMNIROUTE_DEFAULT_OPENCODE_MODELS];
|
||||
assert.ok(defaults.includes("cc/claude-opus-4-8"));
|
||||
assert.ok(
|
||||
defaults.some((m) => m.startsWith("cc/")),
|
||||
"should have cc/ prefixed models"
|
||||
@@ -414,10 +413,9 @@ test("createOmniRouteProvider emits limit.context on default model entries", ()
|
||||
baseURL: "http://localhost:20128",
|
||||
apiKey: "sk_omniroute",
|
||||
});
|
||||
const entry = provider.models["cc/claude-opus-4-8"];
|
||||
const entry = provider.models["cc/claude-opus-4-7"];
|
||||
assert.ok(entry.limit, "model entry should have a limit field");
|
||||
assert.equal(entry.limit!.context, 1_000_000);
|
||||
assert.equal(provider.models["cc/claude-opus-4-7"].limit!.context, 1_000_000);
|
||||
assert.equal(entry.limit!.context, 200_000);
|
||||
});
|
||||
|
||||
test("createOmniRouteProvider omits limit.context for unknown model ids", () => {
|
||||
@@ -430,34 +428,6 @@ test("createOmniRouteProvider omits limit.context for unknown model ids", () =>
|
||||
assert.equal(entry.limit, undefined);
|
||||
});
|
||||
|
||||
test("createOmniRouteProvider reads contextLength from a live model entry for ids absent from the static map", () => {
|
||||
// #3298 regression guard: the static OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS
|
||||
// map only covers the legacy 8 Claude/Gemini ids. Before this change, any
|
||||
// other model got `undefined` context (see the test above, string form) and
|
||||
// OpenCode silently fell back to its 128K internal default. A live model
|
||||
// entry carrying `contextLength` must now surface as `limit.context`.
|
||||
const provider = createOmniRouteProvider({
|
||||
baseURL: "http://localhost:20128",
|
||||
apiKey: "sk_omniroute",
|
||||
models: [{ id: "completely-unknown-model", contextLength: 262_144 }],
|
||||
});
|
||||
const entry = provider.models["completely-unknown-model"];
|
||||
assert.ok(entry.limit, "a live contextLength should produce a limit field even for ids absent from the static map");
|
||||
assert.equal(entry.limit!.context, 262_144);
|
||||
});
|
||||
|
||||
test("createOmniRouteProvider: a live model contextLength wins over the static default map", () => {
|
||||
// `cc/claude-opus-4-8` has a static default (1_000_000). A live entry carrying
|
||||
// a different contextLength must take precedence (live > modelContextLengths >
|
||||
// static defaults).
|
||||
const provider = createOmniRouteProvider({
|
||||
baseURL: "http://localhost:20128",
|
||||
apiKey: "sk_omniroute",
|
||||
models: [{ id: "cc/claude-opus-4-8", contextLength: 524_288 }],
|
||||
});
|
||||
assert.equal(provider.models["cc/claude-opus-4-8"].limit!.context, 524_288);
|
||||
});
|
||||
|
||||
test("createOmniRouteProvider serialises limit.context to JSON", () => {
|
||||
const provider = createOmniRouteProvider({
|
||||
baseURL: "http://localhost:20128",
|
||||
@@ -510,8 +480,8 @@ test("createOmniRouteProvider emits default capability flags inline with the mod
|
||||
baseURL: "http://localhost:20128",
|
||||
apiKey: "sk_omniroute",
|
||||
});
|
||||
const entry = provider.models["cc/claude-opus-4-8"];
|
||||
assert.equal(entry.name, "cc/claude-opus-4-8");
|
||||
const entry = provider.models["cc/claude-opus-4-7"];
|
||||
assert.equal(entry.name, "cc/claude-opus-4-7");
|
||||
assert.equal(entry.attachment, true);
|
||||
assert.equal(entry.reasoning, true);
|
||||
assert.equal(entry.temperature, true);
|
||||
|
||||
178
AGENTS.md
178
AGENTS.md
@@ -3,44 +3,10 @@
|
||||
## Project
|
||||
|
||||
Unified AI proxy/router — route any LLM through one endpoint. Multi-provider support
|
||||
with **229 provider entries** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks,
|
||||
with **212 providers** (OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, Fireworks,
|
||||
Cohere, NVIDIA, Cerebras, Pollinations, Puter, Cloudflare AI, HuggingFace, DeepInfra,
|
||||
SambaNova, Meta Llama API, Moonshot AI, AI21 Labs, Databricks, Snowflake, and many more)
|
||||
with **MCP Server** (69 tools), **A2A v0.3 Protocol**, and **Electron desktop app**.
|
||||
|
||||
> **Live counts (v3.8.16)**: providers 229 · MCP tools 69 · MCP scopes 13 · A2A skills 6 ·
|
||||
> open-sse services 111 · routing strategies 15 · auto-combo scoring factors 12 ·
|
||||
> DB modules 76 · DB migrations 94 · base tables 17 · search providers 12 ·
|
||||
> i18n locales 42. **Refresh with `npm run check:docs-all`.**
|
||||
|
||||
## Doc Accuracy Discipline (read before writing any doc)
|
||||
|
||||
> **If `grep -rn "name" src/ open-sse/ bin/` returns nothing, the name does not exist. Do not document it.**
|
||||
|
||||
The recurring failure mode in AI-generated docs is _plausible-but-unverified specifics_.
|
||||
Every claim in a `.md` file under `docs/` should be verifiable against the source.
|
||||
|
||||
**Rules (enforced by `npm run check:fabricated-docs`):**
|
||||
|
||||
1. **Never state an API name, endpoint, path, CLI command, or env var without grepping for it first.**
|
||||
```bash
|
||||
grep -rn "theName" src/ open-sse/ bin/
|
||||
# 0 hits → do not document
|
||||
```
|
||||
2. **Never write a line count, file size, migration count, provider count, or strategy count from memory.**
|
||||
```bash
|
||||
wc -l <file> # exact line count
|
||||
ls <dir>/*.ts | wc -l # file count
|
||||
```
|
||||
3. **Every code example should be copy-pasted from real usage or actually run** — not synthesized.
|
||||
Link to a real call site (`path:line`) instead of inventing a signature.
|
||||
4. **Prefer citing real source (`file.ts:line`) over paraphrasing behavior** — verifiable and self-correcting.
|
||||
5. **A shorter doc that is 100% accurate beats a comprehensive one with fabrications.**
|
||||
Wrong docs cost more than missing docs, because people trust and act on them.
|
||||
|
||||
The script `scripts/check/check-fabricated-docs.mjs` extracts every route path, env var, hook
|
||||
name, function name, and file reference from `docs/**/*.md` and verifies each one against the
|
||||
codebase. Run it locally before pushing docs; it runs in CI via `npm run check:docs-all`.
|
||||
with **MCP Server** (37 tools), **A2A v0.3 Protocol**, and **Electron desktop app**.
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -49,7 +15,7 @@ codebase. Run it locally before pushing docs; it runs in CI via `npm run check:d
|
||||
- **Database**: better-sqlite3 (SQLite) — `DATA_DIR` configurable, default `~/.omniroute/`
|
||||
- **Streaming**: SSE via `open-sse` internal workspace package
|
||||
- **Styling**: Tailwind CSS v4
|
||||
- **i18n**: next-intl with 42 locales (`src/i18n/messages/`) — refresh with `ls src/i18n/messages/*.json | wc -l`
|
||||
- **i18n**: next-intl with 40+ languages
|
||||
- **Desktop**: Electron (cross-platform: Windows, macOS, Linux)
|
||||
- **Schemas**: Zod v4 for all API / MCP input validation
|
||||
|
||||
@@ -57,32 +23,19 @@ codebase. Run it locally before pushing docs; it runs in CI via `npm run check:d
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------------------- | ------------------------------------------------------------------ |
|
||||
| `npm run dev` | Start Next.js dev server |
|
||||
| `npm run build` | Production build: `next build` → `.build/next/` + assemble `dist/` |
|
||||
| `npm run build:release` | Clean rebuild + HEAD sentinel (`dist/BUILD_SHA`) — use for deploy |
|
||||
| `npm run start` | Run production build |
|
||||
| `npm run build:cli` | Build CLI package |
|
||||
| `npm run lint` | ESLint on all source files |
|
||||
| `npm run typecheck:core` | TypeScript core type checking |
|
||||
| `npm run typecheck:noimplicit:core` | Strict checking (no implicit any) |
|
||||
| `npm run check` | Run lint + test |
|
||||
| `npm run check:cycles` | Check for circular dependencies |
|
||||
| `npm run electron:dev` | Run Electron app in dev mode |
|
||||
| `npm run electron:build` | Build Electron app for current OS |
|
||||
|
||||
**Build output layout:**
|
||||
|
||||
| Directory | Purpose | Gitignored |
|
||||
| --------- | -------------------------------------------------- | ---------- |
|
||||
| `src/` | Application source (TypeScript / TSX) | No |
|
||||
| `.build/` | Build intermediates (`distDir = .build/next`) | Yes |
|
||||
| `dist/` | Shippable bundle assembled by `assembleStandalone` | Yes |
|
||||
|
||||
The pipeline is a single `next build` pass — intermediates land in `.build/next/`, the
|
||||
assembled bundle in `dist/`. VPS deploys rsync `dist/` into the remote
|
||||
`/usr/lib/node_modules/omniroute/app/` directory (VPS image path is unchanged).
|
||||
| Command | Description |
|
||||
| ----------------------------------- | --------------------------------- |
|
||||
| `npm run dev` | Start Next.js dev server |
|
||||
| `npm run build` | Production build (isolated) |
|
||||
| `npm run start` | Run production build |
|
||||
| `npm run build:cli` | Build CLI package |
|
||||
| `npm run lint` | ESLint on all source files |
|
||||
| `npm run typecheck:core` | TypeScript core type checking |
|
||||
| `npm run typecheck:noimplicit:core` | Strict checking (no implicit any) |
|
||||
| `npm run check` | Run lint + test |
|
||||
| `npm run check:cycles` | Check for circular dependencies |
|
||||
| `npm run electron:dev` | Run Electron app in dev mode |
|
||||
| `npm run electron:build` | Build Electron app for current OS |
|
||||
|
||||
### Running Tests
|
||||
|
||||
@@ -178,7 +131,7 @@ Always run `prettier --write` on changed files.
|
||||
|
||||
### Data Layer (`src/lib/db/`)
|
||||
|
||||
All persistence uses SQLite through **76 domain-specific modules** in `src/lib/db/`. Top modules:
|
||||
All persistence uses SQLite through **45+ domain-specific modules** in `src/lib/db/`. Top modules:
|
||||
|
||||
- Core: `core.ts`, `migrationRunner.ts`, `encryption.ts`, `stateReset.ts`
|
||||
- Providers / catalog: `providers.ts`, `models.ts`, `providerLimits.ts`, `compressionAnalytics.ts`
|
||||
@@ -188,17 +141,17 @@ All persistence uses SQLite through **76 domain-specific modules** in `src/lib/d
|
||||
- Storage: `backup.ts`, `cleanup.ts`, `jsonMigration.ts`, `healthCheck.ts`, `databaseSettings.ts`
|
||||
- Extension modules: `evals.ts`, `webhooks.ts`, `reasoningCache.ts`, `readCache.ts`, `tierConfig.ts`, `compressionCombos.ts`, `compressionScheduler.ts`, `batches.ts`, `files.ts`, `syncTokens.ts`, `proxies.ts`, `oneproxy.ts`, `upstreamProxy.ts`, `versionManager.ts`, `cliToolState.ts`, `prompts.ts`, `detailedLogs.ts`, `contextHandoffs.ts`, `compression.ts`, `stats.ts`
|
||||
|
||||
Live count: `ls src/lib/db/*.ts | wc -l` (currently 76). Drift detection: `npm run check:docs-counts`.
|
||||
Schema migrations live in `db/migrations/` (**94 files** as of v3.8.16) and run via `migrationRunner.ts`.
|
||||
Live count: `ls src/lib/db/*.ts | wc -l` (currently 45).
|
||||
Schema migrations live in `db/migrations/` (55 files) and run via `migrationRunner.ts`.
|
||||
`src/lib/localDb.ts` is a **re-export layer only** — never add logic there.
|
||||
|
||||
#### DB Internals
|
||||
|
||||
- **`core.ts`**: `getDbInstance()` returns a singleton `better-sqlite3` instance with WAL
|
||||
journaling. `SCHEMA_SQL` defines **17 base tables** (verify with `grep -c "CREATE TABLE" src/lib/db/core.ts` minus 1 for the bookkeeping `_omniroute_migrations` table). Helpers: `rowToCamel`, `encryptConnectionFields`.
|
||||
journaling. `SCHEMA_SQL` defines 15 base tables. Helpers: `rowToCamel`, `encryptConnectionFields`.
|
||||
- **`migrationRunner.ts`**: Applies versioned SQL files from `db/migrations/` inside transactions.
|
||||
Tracks applied migrations in `_omniroute_migrations` table.
|
||||
- **Migrations**: 94 files (`001_initial_schema.sql` → `094_*.sql`).
|
||||
- **Migrations**: 55 files (`001_initial_schema.sql` → `055_command_code_auth_sessions.sql`).
|
||||
Each migration is idempotent and runs in a transaction. Live count: `ls src/lib/db/migrations/*.sql | wc -l`.
|
||||
- **Domain modules** import `getDbInstance()` from `core.ts` for all CRUD operations.
|
||||
Each module owns a specific table/set of tables (e.g., `providers.ts` → `provider_connections`,
|
||||
@@ -214,19 +167,19 @@ Route → CORS preflight → Body validation (Zod) → Optional auth (extractApi
|
||||
→ API key policy enforcement (enforceApiKeyPolicy) → Handler delegation (open-sse)
|
||||
```
|
||||
|
||||
| Route | Handler | Notes |
|
||||
| ------------------------------- | ------------------------- | ------------------------------------------------------------- |
|
||||
| `chat/completions/route.ts` | `handleChat()` | + prompt injection guard (clones request) |
|
||||
| `responses/route.ts` | `handleChat()` (unified) | Responses API format |
|
||||
| `embeddings/route.ts` | `handleEmbedding()` | Model listing + creation |
|
||||
| `images/generations/route.ts` | `handleImageGeneration()` | Model listing + creation |
|
||||
| `audio/transcriptions/route.ts` | audio handler | Multipart form data |
|
||||
| `audio/speech/route.ts` | TTS handler | Binary audio response |
|
||||
| `videos/generations/route.ts` | video handler | ComfyUI/SD WebUI |
|
||||
| `music/generations/route.ts` | music handler | ComfyUI workflows |
|
||||
| `moderations/route.ts` | moderation handler | Content safety |
|
||||
| `rerank/route.ts` | rerank handler | Document relevance |
|
||||
| `search/route.ts` | search handler | Web search (12 providers per `open-sse/handlers/search.ts:6`) |
|
||||
| Route | Handler | Notes |
|
||||
| ------------------------------- | ------------------------- | ----------------------------------------- |
|
||||
| `chat/completions/route.ts` | `handleChat()` | + prompt injection guard (clones request) |
|
||||
| `responses/route.ts` | `handleChat()` (unified) | Responses API format |
|
||||
| `embeddings/route.ts` | `handleEmbedding()` | Model listing + creation |
|
||||
| `images/generations/route.ts` | `handleImageGeneration()` | Model listing + creation |
|
||||
| `audio/transcriptions/route.ts` | audio handler | Multipart form data |
|
||||
| `audio/speech/route.ts` | TTS handler | Binary audio response |
|
||||
| `videos/generations/route.ts` | video handler | ComfyUI/SD WebUI |
|
||||
| `music/generations/route.ts` | music handler | ComfyUI workflows |
|
||||
| `moderations/route.ts` | moderation handler | Content safety |
|
||||
| `rerank/route.ts` | rerank handler | Document relevance |
|
||||
| `search/route.ts` | search handler | Web search (5 providers) |
|
||||
|
||||
**No global Next.js middleware file** — interception is route-specific. Auth is optional
|
||||
(controlled by `REQUIRE_API_KEY` env). Prompt injection guard is unique to chat completions.
|
||||
@@ -336,8 +289,7 @@ Includes request/response translators with helpers for image handling.
|
||||
|
||||
### Services (`open-sse/services/`)
|
||||
|
||||
111 service modules in `open-sse/services/` (top-level only; 171 including sub-dirs like `autoCombo/` and `compression/`). Refresh: `ls open-sse/services/*.ts | wc -l`. Key modules:
|
||||
`combo.ts` (routing engine), `usage.ts`, `tokenRefresh.ts`,
|
||||
36+ service modules including: `combo.ts` (routing engine), `usage.ts`, `tokenRefresh.ts`,
|
||||
`rateLimitManager.ts`, `accountFallback.ts`, `sessionManager.ts`, `wildcardRouter.ts`,
|
||||
`autoCombo/`, `intentClassifier.ts`, `taskAwareRouter.ts`, `thinkingBudget.ts`,
|
||||
`contextManager.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`,
|
||||
@@ -378,8 +330,8 @@ Modular prompt compression that runs proactively before the existing reactive co
|
||||
and iterates through targets in order until one succeeds or all fail.
|
||||
- **`resolveComboTargets()`**: Expands a combo configuration into an ordered array of
|
||||
`ResolvedComboTarget[]`, each specifying provider + model + account + credentials.
|
||||
- **Strategies** (15): priority, weighted, fill-first, round-robin, P2C, random, least-used, reset-aware (v3.8),
|
||||
reset-window, cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay. Source: `ROUTING_STRATEGY_VALUES` in `src/shared/constants/routingStrategies.ts`.
|
||||
- **Strategies** (14): priority, weighted, fill-first, round-robin, P2C, random, least-used, reset-aware (v3.8),
|
||||
cost-optimized, strict-random, auto, lkgp, context-optimized, context-relay.
|
||||
- Each target calls **`handleSingleModel()`** which wraps `handleChatCore()` with
|
||||
per-target error handling and circuit breaker checks.
|
||||
|
||||
@@ -391,7 +343,7 @@ Policy engine modules: `policyEngine.ts`, `comboResolver.ts`, `costRules.ts`,
|
||||
|
||||
### MCP Server (`open-sse/mcp-server/`)
|
||||
|
||||
69 tools across 10 tool files (advancedTools: 5, agentSkillTools: 3, compressionTools: 5, gamificationTools: 8, memoryTools: 3, notionTools: 6, obsidianTools: 22, pluginTools: 8, poolTools: 5, skillTools: 4), 3 transports (stdio / SSE / Streamable HTTP). Scoped auth (13 scopes — see `OMNIROUTE_MCP_SCOPES` in `open-sse/mcp-server/README.md:51`), Zod schemas. See [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md).
|
||||
37 tools (30 base + 3 memory + 4 skills), 3 transports (stdio / SSE / Streamable HTTP). Scoped auth (~13 scopes), Zod schemas. See [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md).
|
||||
|
||||
**Core tools** (20): get_health, list_combos, get_combo_metrics, switch_combo, check_quota,
|
||||
route_request, cost_report, list_models_catalog, web_search, simulate_route, set_budget_guard,
|
||||
@@ -417,7 +369,7 @@ handler: async (args) => {...} }`. Zod validates inputs before the handler fires
|
||||
`createMcpServer()` wires all tool sets; `startMcpStdio()` launches the stdio transport.
|
||||
- **Transports**: stdio (CLI `omniroute --mcp`), SSE (`/api/mcp/sse`), Streamable HTTP
|
||||
(`/api/mcp/stream`). All share the same tool/scope engine.
|
||||
- **Scopes** (13): Control which tool categories an API key can access. Enforcement happens
|
||||
- **Scopes** (10): Control which tool categories an API key can access. Enforcement happens
|
||||
before handler dispatch.
|
||||
- **Audit**: Every tool invocation is logged to SQLite (`mcp_audit` table) with tool name,
|
||||
args, success/failure, API key attribution, and timestamp.
|
||||
@@ -426,7 +378,7 @@ handler: async (args) => {...} }`. Zod validates inputs before the handler fires
|
||||
|
||||
JSON-RPC 2.0, SSE streaming, Task Manager with TTL cleanup.
|
||||
Agent Card at `/.well-known/agent.json`.
|
||||
Skills (6): `smartRouting.ts`, `quotaManagement.ts`, `providerDiscovery.ts`, `costAnalysis.ts`, `healthReport.ts`, `listCapabilities.ts`.
|
||||
Skills (5): `smartRouting.ts`, `quotaManagement.ts`, `providerDiscovery.ts`, `costAnalysis.ts`, `healthReport.ts`.
|
||||
|
||||
#### A2A Internals
|
||||
|
||||
@@ -527,31 +479,31 @@ Cloudflare Quick/Named, ngrok, Tailscale Funnel. See [`docs/ops/TUNNELS_GUIDE.md
|
||||
|
||||
For any non-trivial change, read the matching deep-dive first:
|
||||
|
||||
| Area | Doc |
|
||||
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Repo navigation | [`docs/architecture/REPOSITORY_MAP.md`](docs/architecture/REPOSITORY_MAP.md) |
|
||||
| Architecture | [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) |
|
||||
| Engineering reference | [`docs/architecture/CODEBASE_DOCUMENTATION.md`](docs/architecture/CODEBASE_DOCUMENTATION.md) |
|
||||
| Auto-Combo (12-factor, 15 strategies) | [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md) |
|
||||
| Resilience (3 layers) | [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) |
|
||||
| Skills | [`docs/frameworks/SKILLS.md`](docs/frameworks/SKILLS.md) |
|
||||
| Memory | [`docs/frameworks/MEMORY.md`](docs/frameworks/MEMORY.md) |
|
||||
| Cloud agents | [`docs/frameworks/CLOUD_AGENT.md`](docs/frameworks/CLOUD_AGENT.md) |
|
||||
| Guardrails | [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) |
|
||||
| Evals | [`docs/frameworks/EVALS.md`](docs/frameworks/EVALS.md) |
|
||||
| Compliance | [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) |
|
||||
| Webhooks | [`docs/frameworks/WEBHOOKS.md`](docs/frameworks/WEBHOOKS.md) |
|
||||
| Authz | [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) |
|
||||
| Stealth | [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) |
|
||||
| Reasoning replay | [`docs/routing/REASONING_REPLAY.md`](docs/routing/REASONING_REPLAY.md) |
|
||||
| Agent protocols (A2A / ACP / Cloud) | [`docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`](docs/frameworks/AGENT_PROTOCOLS_GUIDE.md) |
|
||||
| MCP server | [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md) |
|
||||
| A2A server | [`docs/frameworks/A2A-SERVER.md`](docs/frameworks/A2A-SERVER.md) |
|
||||
| API reference | [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) + [`docs/reference/openapi.yaml`](docs/reference/openapi.yaml) |
|
||||
| Provider catalog (auto-generated) | [`docs/reference/PROVIDER_REFERENCE.md`](docs/reference/PROVIDER_REFERENCE.md) |
|
||||
| Tunnels | [`docs/ops/TUNNELS_GUIDE.md`](docs/ops/TUNNELS_GUIDE.md) |
|
||||
| Electron desktop | [`docs/guides/ELECTRON_GUIDE.md`](docs/guides/ELECTRON_GUIDE.md) |
|
||||
| Release flow | [`docs/ops/RELEASE_CHECKLIST.md`](docs/ops/RELEASE_CHECKLIST.md) |
|
||||
| Area | Doc |
|
||||
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Repo navigation | [`docs/architecture/REPOSITORY_MAP.md`](docs/architecture/REPOSITORY_MAP.md) |
|
||||
| Architecture | [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) |
|
||||
| Engineering reference | [`docs/architecture/CODEBASE_DOCUMENTATION.md`](docs/architecture/CODEBASE_DOCUMENTATION.md) |
|
||||
| Auto-Combo (9-factor, 14 strategies) | [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md) |
|
||||
| Resilience (3 layers) | [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) |
|
||||
| Skills | [`docs/frameworks/SKILLS.md`](docs/frameworks/SKILLS.md) |
|
||||
| Memory | [`docs/frameworks/MEMORY.md`](docs/frameworks/MEMORY.md) |
|
||||
| Cloud agents | [`docs/frameworks/CLOUD_AGENT.md`](docs/frameworks/CLOUD_AGENT.md) |
|
||||
| Guardrails | [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) |
|
||||
| Evals | [`docs/frameworks/EVALS.md`](docs/frameworks/EVALS.md) |
|
||||
| Compliance | [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) |
|
||||
| Webhooks | [`docs/frameworks/WEBHOOKS.md`](docs/frameworks/WEBHOOKS.md) |
|
||||
| Authz | [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) |
|
||||
| Stealth | [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) |
|
||||
| Reasoning replay | [`docs/routing/REASONING_REPLAY.md`](docs/routing/REASONING_REPLAY.md) |
|
||||
| Agent protocols (A2A / ACP / Cloud) | [`docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`](docs/frameworks/AGENT_PROTOCOLS_GUIDE.md) |
|
||||
| MCP server | [`docs/frameworks/MCP-SERVER.md`](docs/frameworks/MCP-SERVER.md) |
|
||||
| A2A server | [`docs/frameworks/A2A-SERVER.md`](docs/frameworks/A2A-SERVER.md) |
|
||||
| API reference | [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) + [`docs/reference/openapi.yaml`](docs/reference/openapi.yaml) |
|
||||
| Provider catalog (auto-generated) | [`docs/reference/PROVIDER_REFERENCE.md`](docs/reference/PROVIDER_REFERENCE.md) |
|
||||
| Tunnels | [`docs/ops/TUNNELS_GUIDE.md`](docs/ops/TUNNELS_GUIDE.md) |
|
||||
| Electron desktop | [`docs/guides/ELECTRON_GUIDE.md`](docs/guides/ELECTRON_GUIDE.md) |
|
||||
| Release flow | [`docs/ops/RELEASE_CHECKLIST.md`](docs/ops/RELEASE_CHECKLIST.md) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
1009
CHANGELOG.md
1009
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
35
CLAUDE.md
35
CLAUDE.md
@@ -11,7 +11,7 @@ npm run build # Production build (Next.js 16 standalone)
|
||||
npm run lint # ESLint (0 errors expected; warnings are pre-existing)
|
||||
npm run typecheck:core # TypeScript check (should be clean)
|
||||
npm run typecheck:noimplicit:core # Strict check (no implicit any)
|
||||
npm run test:coverage # Unit tests + coverage gate (60/60/60/60 — statements/lines/functions/branches)
|
||||
npm run test:coverage # Unit tests + coverage gate (75/75/75/70 — statements/lines/functions/branches)
|
||||
npm run check # lint + test combined
|
||||
npm run check:cycles # Detect circular dependencies
|
||||
```
|
||||
@@ -47,7 +47,7 @@ For full test matrix, see `CONTRIBUTING.md` → "Running Tests". For deep archit
|
||||
| Services | `open-sse/services/` | Combo routing, rate limits, caching, etc |
|
||||
| Database | `src/lib/db/` | SQLite domain modules (45+ files, 55 migrations) |
|
||||
| Domain/Policy | `src/domain/` | Policy engine, cost rules, fallback logic |
|
||||
| MCP Server | `open-sse/mcp-server/` | 43 tools (30 base + 3 memory + 4 skills + 6 notion), 3 transports, ~13 scopes |
|
||||
| MCP Server | `open-sse/mcp-server/` | 37 tools (30 base + 3 memory + 4 skills), 3 transports, ~13 scopes |
|
||||
| A2A Server | `src/lib/a2a/` | JSON-RPC 2.0 agent protocol |
|
||||
| Skills | `src/lib/skills/` | Extensible skill framework |
|
||||
| Memory | `src/lib/memory/` | Persistent conversational memory |
|
||||
@@ -366,23 +366,14 @@ For any non-trivial change, read the matching deep-dive first:
|
||||
| E2E (Playwright) | `npm run test:e2e` |
|
||||
| Protocol E2E (MCP+A2A) | `npm run test:protocols:e2e` |
|
||||
| Ecosystem | `npm run test:ecosystem` |
|
||||
| Coverage gate | `npm run test:coverage` (60/60/60/60 — statements/lines/functions/branches) |
|
||||
| Coverage gate | `npm run test:coverage` (75/75/75/70 — statements/lines/functions/branches) |
|
||||
| Coverage report | `npm run coverage:report` |
|
||||
|
||||
**PR rule**: If you change production code in `src/`, `open-sse/`, `electron/`, or `bin/`, you must include or update tests in the same PR.
|
||||
|
||||
**Test layer preference**: unit first → integration (multi-module or DB state) → e2e (UI/workflow only). Encode bug reproductions as automated tests before or alongside the fix.
|
||||
|
||||
**Both test runners must pass**: `npm run test:unit` (Node native — most tests) AND `npm run test:vitest` (MCP server, autoCombo, cache) cover **non-overlapping files**. Both must be green before merging. A PR where only one suite passes may silently ship broken MCP tools or routing regressions.
|
||||
|
||||
**Bug fix / issue triage protocol (Hard Rule #18)**: Every fix for a reported issue must be validated by one of the following — no exceptions:
|
||||
1. **TDD (preferred)** — write a failing test reproducing the bug → fix it → confirm the test passes. The test becomes the permanent regression guard. Touch only the files the test proves need changing; nothing more.
|
||||
2. **Real-environment test (when TDD is not possible)** — deploy to the production VPS (`root@192.168.0.15`) and run a documented live test. Record the exact command + result in the PR description. Applies to: OAuth upstream flows, Cloudflare/WS upstream behavior, UI-only regressions, hardware-dependent behavior.
|
||||
3. "It worked locally without a test" does not count. A fix without a test or a VPS validation record is not a fix — it is a guess.
|
||||
|
||||
Why this matters: fixing bug A while opening bug B is worse than not fixing at all. The TDD/VPS gate enforces surgical scope — you touch only what the failing test proves is broken. Examples where this paid off: #3090 (claude-web 403), #3113 (WS HTTP fallback), #3052 (heap-guard auto-calibration).
|
||||
|
||||
**Copilot coverage policy**: When a PR changes production code and coverage is below 60% (statements/lines/functions/branches), do not just report — add or update tests, rerun the coverage gate, then ask for confirmation. Include commands run, changed test files, and final coverage result in the PR report.
|
||||
**Copilot coverage policy**: When a PR changes production code and coverage is below 75% (statements/lines/functions) or 70% (branches), do not just report — add or update tests, rerun the coverage gate, then ask for confirmation. Include commands run, changed test files, and final coverage result in the PR report.
|
||||
|
||||
---
|
||||
|
||||
@@ -428,26 +419,12 @@ git push -u origin feat/your-feature
|
||||
6. Never silently swallow errors in SSE streams
|
||||
7. Always validate inputs with Zod schemas
|
||||
8. Always include tests when changing production code
|
||||
9. Coverage must stay ≥60% (statements, lines, functions, branches).
|
||||
9. Coverage must stay ≥75% (statements, lines, functions) / ≥70% (branches). Current measured: ~82%.
|
||||
10. Never bypass Husky hooks (`--no-verify`, `--no-gpg-sign`) without explicit operator approval.
|
||||
11. Never embed public upstream OAuth client_id/secret or Firebase Web keys as string literals — always go through `resolvePublicCred()` (`open-sse/utils/publicCreds.ts`). See `docs/security/PUBLIC_CREDS.md`.
|
||||
12. Never return raw `err.stack` / `err.message` in HTTP / SSE / executor responses — always route through `buildErrorBody()` or `sanitizeErrorMessage()` (`open-sse/utils/error.ts`). See `docs/security/ERROR_SANITIZATION.md`.
|
||||
13. Never string-interpolate external paths or runtime values into shell scripts passed to `exec()`/`spawn()` — pass via the `env` option instead. Reference: `src/mitm/cert/install.ts::updateNssDatabases`.
|
||||
14. Never dismiss a CodeQL / Secret-Scanning alert without (a) first checking the pattern docs above to see if the helper applies, and (b) recording the technical justification in the dismissal comment. Precedent: `js/stack-trace-exposure` raised on callsites that already route through `sanitizeErrorMessage()` is a known CodeQL limitation (custom sanitizers not recognized) — dismiss as `false positive` referencing `docs/security/ERROR_SANITIZATION.md`.
|
||||
15. Never expose routes that spawn child processes (`/api/mcp/`, `/api/cli-tools/runtime/`) without `isLocalOnlyPath()` classification in `src/server/authz/routeGuard.ts`. Loopback enforcement happens unconditionally before any auth check — leaked JWT via tunnel cannot trigger process spawning. See `docs/security/ROUTE_GUARD_TIERS.md`.
|
||||
16. Never include `Co-Authored-By` trailers that credit an AI assistant, LLM, or automation account (e.g. names containing "Claude", "GPT", "Copilot", "Bot"; emails at `anthropic.com` / `openai.com` / bot-owned `noreply.github.com` addresses). Such trailers route attribution to the bot account on GitHub, hiding the real author (`diegosouzapw`) in PR history. Human collaborators — including upstream PR authors and issue reporters being ported into OmniRoute — MAY and SHOULD be credited with standard `Co-authored-by: Name <email>` trailers; the upstream-port workflows (`/port-upstream-features`, `/port-upstream-issues`) depend on this.
|
||||
16. Never include `Co-Authored-By` trailers in commit messages. Commits must appear solely under the repository owner's Git identity (`diegosouzapw`). The `Co-Authored-By: Claude …` line causes GitHub to attribute commits to the `claude` Anthropic account, hiding the real author in the PR history.
|
||||
17. Never expose routes under `/api/services/` or `/dashboard/providers/services/*/embed/` without `isLocalOnlyPath()` classification in `src/server/authz/routeGuard.ts`. These routes can spawn child processes (`npm install`, `node`). Loopback enforcement happens unconditionally before any auth check — a leaked JWT via tunnel cannot trigger process spawning. See `docs/security/ROUTE_GUARD_TIERS.md`.
|
||||
18. Every bug fix must be validated before shipping: a failing-then-passing unit/integration test (TDD) OR a documented live test on the production VPS (192.168.0.15). A fix without either is not merged. See Testing → "Bug fix / issue triage protocol" for the full decision tree.
|
||||
|
||||
---
|
||||
|
||||
## PII & Stream Sanitization Learnings
|
||||
|
||||
### 1. Regex Security (ReDoS)
|
||||
All regex patterns matching variable-length strings (e.g. IPv6 address, credit cards) must use strictly bounded, non-overlapping sequences (e.g., limit occurrences with bounded ranges `{1,7}`) to prevent catastrophic backtracking when processing untrusted inputs.
|
||||
|
||||
### 2. SSE Snapshot Handling
|
||||
When parsing streaming LLM responses (e.g. Responses API), check if a chunk represents a final snapshot (`done` or `completed` events). Snapshot text must be sanitized directly as a standalone string (bypassing rolling delta buffers) to prevent text duplication at the end of the stream.
|
||||
|
||||
### 3. Database Handles in Tests
|
||||
Ensure that any unit tests that trigger database migrations or establish SQLite connections call `resetDbInstance()` and properly clean up/close all DB handles in a `test.after(...)` hook. Failure to release database connection handles will cause Node's native test runner to hang indefinitely.
|
||||
|
||||
@@ -59,40 +59,13 @@ These settings are stored in the database and persist across restarts, overridin
|
||||
npm run dev
|
||||
|
||||
# Production build
|
||||
npm run build # next build → .build/next/ then assembleStandalone → dist/
|
||||
npm run build
|
||||
npm run start
|
||||
|
||||
# Release build (clean rebuild + HEAD sentinel — required for deploy)
|
||||
npm run build:release # rm -rf .build dist && build + writes dist/BUILD_SHA
|
||||
|
||||
# Common port configuration
|
||||
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
|
||||
```
|
||||
|
||||
### Build Output Layout
|
||||
|
||||
| Directory | Contents | Tracked |
|
||||
| ---------- | ------------------------------------------- | ------- |
|
||||
| `src/` | Application source (TypeScript / TSX) | Yes |
|
||||
| `.build/` | Intermediates — `next build` output (gitignored, `distDir = .build/next`) | No |
|
||||
| `dist/` | Shippable bundle — assembled by `assembleStandalone` (gitignored) | No |
|
||||
|
||||
The build pipeline is a single pass:
|
||||
|
||||
```
|
||||
npm run build
|
||||
└─ next build → .build/next/standalone (Next.js output)
|
||||
└─ assembleStandalone() (copies standalone + static + public + native assets)
|
||||
└─ output: dist/ (server.js, .next/static/, public/, node_modules/)
|
||||
```
|
||||
|
||||
`npm run build:release` additionally cleans both directories first and writes
|
||||
`dist/BUILD_SHA` (= `git rev-parse --short HEAD`) as a deploy integrity sentinel.
|
||||
|
||||
> **VPS deploy note:** the remote image directory `/usr/lib/node_modules/omniroute/app/`
|
||||
> is unchanged. The deploy skills rsync the contents of `dist/` into it.
|
||||
> Only the in-repo build output path moved (`app/` → `dist/`).
|
||||
|
||||
Default URLs:
|
||||
|
||||
- **Dashboard**: `http://localhost:20128/dashboard`
|
||||
@@ -259,25 +232,18 @@ tests/
|
||||
├── translator/ # Translator-specific tests
|
||||
└── load/ # Load tests
|
||||
|
||||
docs/
|
||||
├── adr/ # Architecture Decision Records
|
||||
├── architecture/ # System architecture & resilience
|
||||
├── comparison/ # OmniRoute vs alternatives
|
||||
├── compression/ # Compression guides & rules
|
||||
├── dev/ # Development guides
|
||||
├── diagrams/ # Architecture diagrams
|
||||
├── frameworks/ # MCP, A2A, OpenCode, Memory, Skills
|
||||
├── guides/ # User guide, Docker, setup, troubleshooting
|
||||
├── i18n/ # Internationalized README translations
|
||||
├── marketing/ # Marketing materials
|
||||
├── ops/ # Deployment, proxy, coverage, releases
|
||||
├── providers/ # Provider-specific docs
|
||||
├── reference/ # API reference, env vars, CLI tools, free tiers
|
||||
├── releases/ # Release notes
|
||||
├── routing/ # Auto-combo engine, reasoning replay
|
||||
├── screenshots/ # Dashboard screenshots
|
||||
├── security/ # Guardrails, compliance, stealth, tokens
|
||||
└── specs/ # Design specs
|
||||
docs/ # Documentation
|
||||
├── ARCHITECTURE.md # System architecture
|
||||
├── API_REFERENCE.md # All endpoints
|
||||
├── USER_GUIDE.md # Provider setup, CLI integration
|
||||
├── TROUBLESHOOTING.md # Common issues
|
||||
├── MCP-SERVER.md # MCP server (25 tools)
|
||||
├── A2A-SERVER.md # A2A agent protocol
|
||||
├── AUTO-COMBO.md # Auto-combo engine
|
||||
├── CLI-TOOLS.md # CLI tools integration
|
||||
├── COVERAGE_PLAN.md # Test coverage improvement plan
|
||||
├── openapi.yaml # OpenAPI specification
|
||||
└── adr/ # Architecture Decision Records
|
||||
```
|
||||
|
||||
---
|
||||
@@ -341,10 +307,6 @@ Write unit tests in `tests/unit/` covering at minimum:
|
||||
|
||||
Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions.
|
||||
|
||||
For VPS deploys, use `npm run build:release` (not `npm run build`) — it performs a clean
|
||||
rebuild, assembles the bundle into `dist/`, and writes the `dist/BUILD_SHA` sentinel.
|
||||
Then use the `/deploy-vps-*-cc` skills which rsync `dist/` to the remote `app/` directory.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
139
Dockerfile
139
Dockerfile
@@ -1,22 +1,10 @@
|
||||
# ── Common base with runtime deps ──────────────────────────────────────────
|
||||
FROM node:24-trixie-slim AS base
|
||||
FROM node:24-trixie-slim AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=shared \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=shared \
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── Builder ────────────────────────────────────────────────────────────────
|
||||
FROM base AS builder
|
||||
|
||||
# Build tools for native module compilation
|
||||
# apt-get update needed here because base's rm -rf clears the shared cache
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=shared \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=shared \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends python3 make g++ \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
@@ -24,29 +12,26 @@ COPY scripts/build/postinstall.mjs ./scripts/build/postinstall.mjs
|
||||
COPY scripts/build/postinstallSupport.mjs ./scripts/build/postinstallSupport.mjs
|
||||
COPY scripts/build/native-binary-compat.mjs ./scripts/build/native-binary-compat.mjs
|
||||
ENV NPM_CONFIG_LEGACY_PEER_DEPS=true
|
||||
# --ignore-scripts blocks broad dependency install/postinstall hooks, closing
|
||||
# the supply-chain attack surface where a transitive dep can run arbitrary code
|
||||
# at install time. better-sqlite3 still needs a native binding for the target
|
||||
# platform, so rebuild and smoke-test only that known runtime dependency below.
|
||||
# --ignore-scripts blocks the install/postinstall hooks of dependencies,
|
||||
# closing the supply-chain attack surface where a transitive dep can run
|
||||
# arbitrary code at install time. OmniRoute's own postinstall (
|
||||
# better-sqlite3 binary touchups, @swc/helpers copy) is only needed when
|
||||
# a packaged app/node_modules is unpacked — inside the Docker builder we
|
||||
# are doing a fresh native-platform install, so dropping the scripts is safe.
|
||||
#
|
||||
# We REQUIRE a committed package-lock.json so resolved dependency versions
|
||||
# are reproducible.
|
||||
RUN test -f package-lock.json \
|
||||
|| (echo "package-lock.json is required for reproducible Docker builds" >&2 && exit 1)
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci --no-audit --no-fund --legacy-peer-deps --ignore-scripts \
|
||||
&& npm rebuild better-sqlite3 \
|
||||
&& node -e "require('better-sqlite3')(':memory:').close()"
|
||||
|
||||
# Use Turbopack for significant build speedup
|
||||
ENV OMNIROUTE_USE_TURBOPACK=1
|
||||
npm ci --no-audit --no-fund --legacy-peer-deps --ignore-scripts
|
||||
|
||||
COPY . ./
|
||||
RUN --mount=type=cache,target=/app/.build/next/cache \
|
||||
mkdir -p /app/data && npm run build
|
||||
RUN --mount=type=cache,target=/app/.next/cache \
|
||||
mkdir -p /app/data && npm run build -- --webpack
|
||||
|
||||
# ── Runner base ────────────────────────────────────────────────────────────
|
||||
FROM base AS runner-base
|
||||
FROM node:24-trixie-slim AS runner-base
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.title="omniroute" \
|
||||
org.opencontainers.image.description="Unified AI proxy — route any LLM through one endpoint" \
|
||||
@@ -57,32 +42,40 @@ LABEL org.opencontainers.image.title="omniroute" \
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=20128
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV OMNIROUTE_MEMORY_MB=1024
|
||||
ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_MEMORY_MB}"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=256"
|
||||
|
||||
# Data directory inside Docker — must match the volume mount in docker-compose.yml
|
||||
ENV DATA_DIR=/app/data
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
# `npm run build` (build-next-isolated → assembleStandalone) bundles ALL runtime
|
||||
# files into .build/next/standalone/ — .next, node_modules, migrations, scripts,
|
||||
# docs, and the previously hand-COPY'd modules below (@swc/helpers, pino-*, split2,
|
||||
# migrations). assembleStandalone copies them straight from the builder's
|
||||
# node_modules, so they are present regardless of NFT/Turbopack trace behaviour.
|
||||
# The old per-module overrides were therefore pure duplication and were removed
|
||||
# (build-output-isolation cleanup). See scripts/build/assembleStandalone.mjs
|
||||
# (EXTRA_MODULE_ENTRIES) for the single source of truth.
|
||||
COPY --from=builder /app/.build/next/standalone ./
|
||||
# better-sqlite3 is the one exception still copied explicitly: assembleStandalone
|
||||
# only syncs its native build/ dir; the JS wrapper (lib/, package.json) is left to
|
||||
# Next.js tracing. bootstrap-env requires SQLite BEFORE the standalone server
|
||||
# starts, so guarantee the complete package independent of trace behaviour.
|
||||
COPY --from=builder /app/node_modules/better-sqlite3 ./node_modules/better-sqlite3
|
||||
# migrations land at <standalone>/migrations via assembleStandalone; point the runtime at them.
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime
|
||||
COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
|
||||
# Explicitly copy pino transport dependencies — pino spawns a worker that requires
|
||||
# pino-abstract-transport at runtime; Next.js standalone trace does not capture it (#449)
|
||||
COPY --from=builder /app/node_modules/pino-abstract-transport ./node_modules/pino-abstract-transport
|
||||
COPY --from=builder /app/node_modules/pino-pretty ./node_modules/pino-pretty
|
||||
COPY --from=builder /app/node_modules/split2 ./node_modules/split2
|
||||
# Migration SQL files are read via fs.readFileSync at runtime and are NOT
|
||||
# traced by Next.js standalone output — copy them explicitly.
|
||||
COPY --from=builder /app/src/lib/db/migrations ./migrations
|
||||
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
|
||||
# MITM server.cjs is spawned at runtime via child_process — not traced by nft
|
||||
COPY --from=builder /app/src/mitm/server.cjs ./src/mitm/server.cjs
|
||||
# Runtime docs are pruned by .dockerignore to English markdown + OpenAPI.
|
||||
# Next.js standalone tracing does not include docs read via fs.
|
||||
COPY --from=builder /app/.next/standalone/docs ./docs
|
||||
|
||||
# Docker healthcheck script — not traced by Next.js standalone output, so copy
|
||||
# it explicitly. The HEALTHCHECK CMD references it as `node healthcheck.mjs`.
|
||||
COPY --from=builder /app/scripts/dev/run-standalone.mjs ./dev/run-standalone.mjs
|
||||
COPY --from=builder /app/scripts/build/runtime-env.mjs ./build/runtime-env.mjs
|
||||
COPY --from=builder /app/scripts/build/bootstrap-env.mjs ./build/bootstrap-env.mjs
|
||||
COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
|
||||
|
||||
# Hand /app over to the baked-in `node` non-root user (UID/GID 1000) so the
|
||||
@@ -92,61 +85,13 @@ RUN chown -R node:node /app
|
||||
|
||||
EXPOSE 20128
|
||||
|
||||
# Drop to non-root before ENTRYPOINT/CMD so every derived stage (runner-cli,
|
||||
# runner-web) also runs as a non-root user unless they explicitly switch back.
|
||||
USER node
|
||||
|
||||
# Warns if the mounted data volume has wrong ownership
|
||||
COPY --chmod=755 scripts/check-permissions.sh /tmp/check-permissions.sh
|
||||
ENTRYPOINT ["/tmp/check-permissions.sh"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD ["node", "healthcheck.mjs"]
|
||||
|
||||
CMD ["node", "dev/run-standalone.mjs"]
|
||||
|
||||
# ── Runner Web (web-cookie providers: Gemini Web, Claude Turnstile) ───────────
|
||||
#
|
||||
# Two image flavors:
|
||||
# runner-base → omniroute:VERSION Lean base (~500 MB). No browsers.
|
||||
# runner-web → omniroute:VERSION-web +Chromium/Playwright (~800 MB).
|
||||
#
|
||||
# Use runner-web when you need web-cookie providers (gemini-web, claude-web,
|
||||
# claude-turnstile). For all other providers runner-base is sufficient.
|
||||
#
|
||||
# Build:
|
||||
# docker build --target runner-web -t omniroute:web .
|
||||
# Compose:
|
||||
# build:
|
||||
# context: .
|
||||
# target: runner-web
|
||||
FROM runner-base AS runner-web
|
||||
|
||||
USER root
|
||||
|
||||
# Copy playwright and playwright-core from the builder stage.
|
||||
# The slim runtime image does not have playwright in node_modules, so npx falls
|
||||
# back to a registry download — unreliable on CI runners (exits 127 on failure).
|
||||
# Copying from the builder avoids any network access at image-build time and also
|
||||
# ensures the same playwright version is available at runtime for web-session providers.
|
||||
COPY --from=builder /app/node_modules/playwright-core ./node_modules/playwright-core
|
||||
COPY --from=builder /app/node_modules/playwright ./node_modules/playwright
|
||||
|
||||
# Install Playwright browser binaries + OS dependencies under root, then hand
|
||||
# ownership of the browsers cache to the node user.
|
||||
# PLAYWRIGHT_BROWSERS_PATH overrides the default ~/.cache/ms-playwright so the
|
||||
# browsers land under /home/node which persists across image layers and is
|
||||
# accessible to the non-root runtime user.
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
|
||||
apt-get update \
|
||||
&& node node_modules/playwright/cli.js install chromium --with-deps \
|
||||
&& chown -R node:node /home/node/.cache \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER node
|
||||
|
||||
FROM runner-base AS runner-cli
|
||||
|
||||
# Drop back to root briefly so we can install system + global npm packages,
|
||||
|
||||
72
ONBOARDING.md
Normal file
72
ONBOARDING.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Welcome to OmniRoute
|
||||
|
||||
## How We Use Claude
|
||||
|
||||
Based on diegosouzapw's usage over the last 30 days:
|
||||
|
||||
Work Type Breakdown:
|
||||
Build Feature ████████████████████ 50%
|
||||
Plan Design ██████████░░░░░░░░░░ 25%
|
||||
Improve Quality ██████░░░░░░░░░░░░░░ 15%
|
||||
Write Docs ████░░░░░░░░░░░░░░░░ 10%
|
||||
|
||||
Top Skills & Commands:
|
||||
_no slash commands captured in this window_
|
||||
|
||||
Top MCP Servers:
|
||||
_no MCP usage captured in this window_
|
||||
|
||||
## Your Setup Checklist
|
||||
|
||||
### Codebases
|
||||
|
||||
- [ ] omniroute — https://github.com/diegosouzapw/omniroute
|
||||
- [ ] OpenCode_Ecosystem (fork) — https://github.com/diegosouzapw/OpenCode_Ecosystem
|
||||
- [ ] OpenCode_Ecosystem (upstream) — https://github.com/MarceloClaro/OpenCode_Ecosystem
|
||||
|
||||
### MCP Servers to Activate
|
||||
|
||||
- [ ] _none required from current usage. If you'll be working on OmniRoute itself, ask the team about the project's own embedded MCP server at `/api/mcp/stream`._
|
||||
|
||||
### Skills to Know About
|
||||
|
||||
- _no skills surfaced from usage data. The team's workflow leaned heavily on direct file edits, git/gh CLI, and subagent dispatch for parallel work — Claude figures these out from context._
|
||||
|
||||
## Team Tips
|
||||
|
||||
- **Read `CLAUDE.md` first.** It has hard rules that override defaults — e.g. never write raw SQL in routes (use `src/lib/db/` modules), never add `Co-Authored-By: Claude` to commits, error responses must go through `buildErrorBody()` / `sanitizeErrorMessage()`.
|
||||
- **Subagents for parallel work.** When tasks are independent, dispatch multiple sonnet subagents in one message instead of doing them yourself. Always audit `git diff` after a subagent finishes — its summary describes intent, not necessarily the result.
|
||||
- **Conventional Commits** for everything: `feat(scope):`, `fix(scope):`, `chore(scope):`, `docs:`. Scopes used here include `db`, `sse`, `oauth`, `dashboard`, `api`, `agents`, `plugin`, `skills`, `commands`.
|
||||
- **Run the full validation suite before declaring done** — `npm run check` (lint + tests) at minimum; `npm run test:coverage` if you changed production code. Hard gate: 75/75/75/70 (statements/lines/functions/branches).
|
||||
- **Husky pre-push runs unit tests.** Don't `--no-verify` past it without explicit approval — the project documents this as a hard rule.
|
||||
|
||||
## Get Started
|
||||
|
||||
- Clone the repo and run `npm install` (auto-generates `.env` from `.env.example`).
|
||||
- Generate secrets: `openssl rand -base64 48` for `JWT_SECRET`, `openssl rand -hex 32` for `API_KEY_SECRET`. Paste into `.env`.
|
||||
- `npm run dev` → dashboard at `http://localhost:20128`.
|
||||
- Read `docs/architecture/REPOSITORY_MAP.md` for the file layout, then `docs/architecture/ARCHITECTURE.md` for how requests flow.
|
||||
- For a first PR: pick something from `_tasks/` if there's a backlog, or grep for `// TODO` and pick a small one. Run `npm run check` before opening the PR.
|
||||
|
||||
<!-- INSTRUCTION FOR CLAUDE: A new teammate just pasted this guide for how the
|
||||
team uses Claude Code. You're their onboarding buddy — warm, conversational,
|
||||
not lecture-y.
|
||||
|
||||
Open with a warm welcome — include the team name from the title. Then: "Your
|
||||
teammate uses Claude Code for Build Feature, Plan Design, Improve Quality, and
|
||||
Write Docs work. Let's get you started."
|
||||
|
||||
Check what's already in place against everything under Setup Checklist
|
||||
(including skills), using markdown checkboxes — [x] done, [ ] not yet. Lead
|
||||
with what they already have. One sentence per item, all in one message.
|
||||
|
||||
Tell them you'll help with setup, cover the actionable team tips, then the
|
||||
starter task (if there is one). Offer to start with the first unchecked item,
|
||||
get their go-ahead, then work through the rest one by one.
|
||||
|
||||
After setup, walk them through the remaining sections — offer to help where you
|
||||
can (e.g. link to channels), and just surface the purely informational bits.
|
||||
|
||||
Don't invent sections or summaries that aren't in the guide. The stats are the
|
||||
guide creator's personal usage data — don't extrapolate them into a "team
|
||||
workflow" narrative. -->
|
||||
@@ -1,209 +0,0 @@
|
||||
# Fase 6A — Auditoria Crítica das Fases 0–6: o que deixamos passar
|
||||
|
||||
> **Para workers agênticos:** SUB-SKILL OBRIGATÓRIA: `superpowers:subagent-driven-development` (recomendado) ou `superpowers:executing-plans`, tarefa-a-tarefa. Tarefas P0/P1 maiores devem ser **expandidas em sub-plano bite-sized próprio** (`writing-plans`) no momento da execução. Hard Rule #18 (TDD/VPS) em tudo. Auditar subagentes (trust-but-verify) após cada task.
|
||||
|
||||
> # ⏳ PORTÃO DE ATIVAÇÃO — NÃO INICIAR ANTES DE **2026-06-16**
|
||||
> Mesma janela da Fase 7 (decisão do owner 2026-06-09: 1 semana de uso em produção das Fases 0–6 antes de evoluir). **Ordem na ativação: Fase 6A ANTES da Fase 7** — primeiro consertamos/endurecemos o que já existe, depois adicionamos ferramentas novas.
|
||||
> **Exceção possível (decisão do owner):** as tasks **6A.1 e 6A.2 são bugs pré-existentes descobertos pela auditoria** (testes que nunca rodam + suíte vitest fora do CI), não "gates novos" — podem ser antecipados como fix avulso se o owner preferir não esperar a janela.
|
||||
|
||||
**Goal:** Fechar os furos que a auditoria crítica pós-implementação (2026-06-09, análise inline dos 18 gates + motor + CI + baselines) encontrou nas Fases 0–6 — antes de adicionar qualquer ferramenta nova na Fase 7.
|
||||
|
||||
**Architecture:** Zero ferramenta nova (tudo homegrown, padrão `check-*.mjs` + motor existente). Três frentes: (1) **bugs sistêmicos de runner** descobertos (testes órfãos, vitest fora do CI); (2) **endurecimento do padrão de catraca** (stale-allowlist enforcement + require-tighten, validados pela prática da Notion); (3) **expansão de escopo** dos gates existentes (diretórios/superfícies que ficaram de fora).
|
||||
|
||||
**Tech Stack:** Node ≥20 ESM, ESLint 9 flat, c8, jscpd@4 (a pinar), GitHub Actions, Node native test runner, vitest. Nada novo em `dependency-allowlist.json` exceto a promoção do jscpd a devDependency (Task 6A.12).
|
||||
|
||||
---
|
||||
|
||||
## Origem: o que a auditoria encontrou (resumo dos achados)
|
||||
|
||||
Método: releitura inline de todos os `scripts/check/*.{mjs,ts}` criados nas Fases 0–6, `scripts/quality/*`, baselines, `ci.yml`, `package.json`, hooks Husky e docs — sem subagentes — mais validação por pesquisa (sistema de ratcheting da Notion; práticas de suppression-hygiene de linters).
|
||||
|
||||
| # | Achado | Gravidade | Task |
|
||||
|---|--------|-----------|------|
|
||||
| A1 | **≈135 arquivos `*.test.ts` em subdiretórios de `tests/unit/` não são coletados por NENHUM runner** — `test:unit`, `test:coverage` e os shards do CI usam o glob não-recursivo `tests/unit/*.test.ts`; o vitest só inclui `autoCombo/**` (+ `.tsx`). Inclui `authz/routeGuard.test.ts` (Hard Rules #15/#17), 50 testes de `compression/`, 12 de `services/`, 10 de `gamification/`, 6 de `guardrails/`, 5 de `security/`. **Amostra rodada na auditoria: 2 asserts de `routeGuard.test.ts` FALHAM hoje** ("management policy allows /api/services/ (e /api/copilot/chat) from localhost with valid CLI token") — o arquivo apodreceu sem ninguém ver, provavelmente desde o redesign do peer-stamp (2026-05-31) | **P0 — falso verde sistêmico** | 6A.1 |
|
||||
| A2 | **Nenhum workflow roda `test:vitest`** (`grep -rln vitest .github/workflows/` = vazio). O CLAUDE.md afirma "Both test runners must pass… before merging", mas a suíte vitest (MCP server 43 tools, autoCombo, cache, componentes) está 100% fora da esteira | **P0** | 6A.2 |
|
||||
| B1 | **Nenhum gate falha quando uma entrada de allowlist deixa de ser necessária.** Os 18 gates congelam ~90 violações em `KNOWN_*`; quando alguém corrige a violação (ex.: criar `/api/gamification/level` da issue #3484, remover `krutrim` da #3483), a entrada vira um furo aberto — a regressão pode VOLTAR sem revisão. Só `check-error-helper` tem detecção parcial (WARN de arquivo inexistente, que ninguém lê). Prática validada: linters maduros "yell when an exclusion exists that doesn't break the rule" | **P0 — corrói a catraca com o tempo** | 6A.3 |
|
||||
| B2 | **Melhoria não-capturada vira folga permanente no motor**: sem `--update` manual, uma métrica que melhorou pode regredir de volta até o baseline antigo sem ninguém ver. A Notion auto-decrementa budgets no pre-commit; nosso motor não exige aperto | P1 | 6A.5 |
|
||||
| B3 | EPS único (0.01) para todas as métricas; o plano da Fase 4 pedia epsilon maior para `coverage.branches` (não-determinismo do v8) e não foi implementado | P1 | 6A.5 |
|
||||
| B4 | Métrica coletada sem entrada no baseline é ignorada em silêncio (coletor novo esquecido do baseline = falso conforto) | P2 | 6A.5 |
|
||||
| C1 | `check-fetch-targets` só varre `src/app/(dashboard)` — **20+ arquivos com `fetch("/api/…")` fora do escopo**: `src/shared/components/` (Sidebar, CommandPalette, modais…), `src/app/connect/`, `src/app/status/`, `src/lib/evals/` | P1 | 6A.7 |
|
||||
| C2 | `check-fetch-targets` ignora 100% dos template literals (`` fetch(`/api/x/${id}`) ``) — nem o prefixo estático é validado | P1 | 6A.7 |
|
||||
| C3 | `check-fetch-targets` não valida o método HTTP (fetch `POST` → rota só com `GET` = 405 em runtime, gate verde) | P2 | 6A.7 |
|
||||
| C4 | `check-deps` cobre só `package.json` raiz + `electron/` — **`@omniroute/opencode-plugin` (dep `zod` + 5 devDeps, pacote PUBLICADO no npm), `@omniroute/opencode-provider` e `open-sse/` ficam fora** | P1 | 6A.8 |
|
||||
| C5 | `check-public-creds` escaneia só 2 arquivos hardcoded — credencial literal em arquivo NOVO (executor, oauth provider) passa batida | P1 | 6A.8 |
|
||||
| C6 | `check-error-helper` cobre `open-sse/executors` + `handlers` — a Hard Rule #12 também fala de **MCP handlers** (`open-sse/mcp-server/`) e rotas HTTP (`src/app/api/`), fora do escopo | P1 | 6A.8 |
|
||||
| C7 | `check-file-size` e `check-complexity` varrem `src` + `open-sse` — `electron/` e `bin/` fora (god-file pode nascer lá) | P2 | 6A.11 |
|
||||
| C8 | `check-known-symbols` cobre executors/strategies/translators — **faltam as 3 superfícies de despacho por-string restantes: MCP tools (43), A2A skills (5, `A2A_SKILL_HANDLERS`), cloud agents (3, registry)** | P1 | 6A.9 |
|
||||
| C10 | `check-route-guard-membership` depende da lista manual `SPAWN_CAPABLE_ROUTE_ROOTS` (3 raízes) — rota nova que spawna processo FORA dessas raízes é invisível ao gate | P1 | 6A.8 |
|
||||
| C11 | `check-openapi-coverage` (THRESHOLD=36%) e `check-ui-keys-coverage` (65%) são pisos fixos manuais — não ratcheteiam; rotas/strings novas sem doc/i18n passam enquanto o % não cai do piso | P2 | 6A.11 |
|
||||
| C12 | `check-test-masking`: (a) `--diff-filter=M` não vê teste **DELETADO** (o masking mais brutal); (b) não vê `.skip`/`.todo`/`.only` adicionados (mantêm os asserts no texto, mas nunca rodam); (c) tautologia só cobre `assert.ok(true)` | P1 | 6A.10 |
|
||||
| D1 | **`ci.yml` roda gates apenas em `pull_request → main`** — todo o ciclo de PRs feature→`release/vX` passa SEM gate; as violações acumulam por semanas e estouram juntas no merge release→main (observado no gate da v3.8.18: 4 fixes de typecheck + ReDoS de última hora) | P1 — decisão do owner | 6A.6 |
|
||||
| D2 | `.husky/pre-push` está 100% comentado, mas o CLAUDE.md afirma "pre-push: npm run test:unit" (drift doc↔real) | P2 | 6A.12 |
|
||||
| D3 | **CLAUDE.md não menciona nenhum dos 18 gates** — um agente futuro não sabe que existem, qual a política de allowlist ("corrija, não congele"), nem como apertar baselines. Hard Rule #9 ainda diz "≥60%" (a catraca real é 80/80/82/73) | **P0 — anti-alucinação para os próprios agentes** | 6A.4 |
|
||||
| D4 | `check-duplication` roda `npx --yes jscpd@4` — pacote **não pinado por lockfile**, baixado do registry a cada run do CI (supply-chain + flakiness + latência); contraria o espírito do próprio `check-deps` | P2 | 6A.12 |
|
||||
| D5 | A skill `/quality-scan` roda ~20 comandos um a um — falta um runner agregador paralelo | P2 | 6A.12 |
|
||||
| D6 | Baselines de coverage com folga de ~2,5pt (80/80/82/73 vs real ~82,6/82,6/84,2/75,2) — a nota "_aperte após o 1º run verde_" existe no JSON mas não é tarefa de ninguém | P1 | 6A.5 |
|
||||
| E4 | Sem guarda contra artefato trackeado por engano — `node_modules` symlink já foi commitado 2× neste repo (`git add -A` em worktree) | P2 | 6A.12 |
|
||||
|
||||
**Decisões conscientes de NÃO fazer (avaliadas e descartadas, com motivo):**
|
||||
- **Gate de idempotência de migrations via regex** — SQLite não tem `ADD COLUMN IF NOT EXISTS`; idempotência vive em try/catch do runner; regex seria frágil (FP/FN). O `check-migration-numbering` + revisão humana bastam.
|
||||
- **Endurecer `check-docs-counts-sync` para fail** — contagens em prosa são heurísticas; soft-fail é o design correto. A cobertura de MCP tools entra pela 6A.9 (símbolos, não prosa).
|
||||
- **Sentido inverso do provider-consistency (providers.ts → REGISTRY)** — muitos providers canônicos legitimamente não têm entrada no REGISTRY (web/OAuth-only); a allowlist nasceria com dezenas de entradas e baixa razão sinal/ruído. Reavaliar quando o refactor #3501 tocar o split de providers.
|
||||
- **Complexidade por-função/por-arquivo (formato any-budget)** — upgrade real, mas o count global + `max-lines-per-function` já bloqueiam o grosso; o formato per-file entra junto com `sonarjs/cognitive-complexity` na Fase 7 Task 5 para não pagar duas migrações de baseline.
|
||||
|
||||
---
|
||||
|
||||
# Tasks
|
||||
|
||||
## P0 — bugs sistêmicos + documentação
|
||||
|
||||
### Task 6A.1 — `check-test-discovery` + religamento triado dos ~135 testes órfãos ⭐
|
||||
|
||||
**O achado nº1 da auditoria.** Testes que não rodam são o falso verde definitivo — todo o investimento anti test-masking da Fase 4 protege asserts de testes que **nem executam**.
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/check/check-test-discovery.mjs` + `tests/unit/check-test-discovery.test.ts`
|
||||
- Modify: `package.json` (globs de `test:unit`, `test:coverage`), `.github/workflows/ci.yml` (globs dos shards 8×/node24/node26), `vitest.mcp.config.ts` ou `vitest.config.ts` (se algum subdir for re-homed para vitest)
|
||||
- Create: `test-discovery-baseline.json` (órfãos ainda-não-religados, catraca `down` até zerar)
|
||||
|
||||
**Approach (expandir em sub-plano na execução):**
|
||||
1. **Gate primeiro (TDD):** `check-test-discovery.mjs` enumera todo `**/*.{test,spec}.{ts,tsx}` do repo (fora de `node_modules`/`.next`) e verifica que cada arquivo é coletado por ≥1 runner: (a) globs do node test runner extraídos de `package.json`/`ci.yml`; (b) `include` dos dois `vitest.*config.ts`; (c) projetos Playwright. Órfão fora do baseline → exit 1. O baseline congela os órfãos atuais (catraca: não pode SUBIR; religamentos a fazem cair até `{}`).
|
||||
2. **Inventário verde/vermelho:** rodar cada subdir órfão isoladamente (`node --import tsx --test tests/unit/<dir>/*.test.ts`), registrar passa/falha. *Não* ligar tudo de uma vez — a amostra já provou que há vermelhos (`authz/routeGuard.test.ts`: 2 asserts).
|
||||
3. **Religar os verdes:** trocar o glob principal para recursivo — `"tests/unit/**/*.test.ts"` ENTRE ASPAS (expandido pelo test runner do Node, não pelo shell; **Step 0: validar o suporte a glob do runner na menor versão de Node suportada pelo repo** — fallback: listar os subdirs explicitamente) — em `test:unit`, `test:coverage` e nos 3 lugares do `ci.yml` (shards 8×, node24, node26). Remover religados do baseline.
|
||||
4. **Triar os vermelhos (Hard Rule #18 em cada um):** teste desatualizado → atualizar para o comportamento real (e provar que o comportamento real é o desejado); bug real revelado → fix TDD; teste de feature morta → deletar com justificativa no commit. Os 2 asserts do `routeGuard.test.ts` ("allows … with valid CLI token") são o primeiro caso: provável drift do peer-stamp de 2026-05-31 — MAS, por ser superfície de segurança (#15/#17), confirmar com cuidado que é o teste que está errado, não o guard.
|
||||
5. **Recalibrar cobertura:** religar ≈135 arquivos muda o denominador/numerador da cobertura — re-medir e apertar `quality-baseline.json` via `--update` no mesmo PR (resolve também o D6).
|
||||
|
||||
**Acceptance:** `check-test-discovery` no CI (job lint); zero órfãos fora do baseline; baseline decrescente documentado; suíte verde com os religados; cobertura recalibrada.
|
||||
|
||||
### Task 6A.2 — vitest no CI
|
||||
|
||||
**Files:** `.github/workflows/ci.yml` (job novo `test-vitest`, paralelo aos shards; NÃO tocar nos triggers — apenas adicionar job).
|
||||
|
||||
**Approach:** job com `npm ci` + `npm run test:vitest` (+ `test:vitest:ui` se o tempo couber; senão segundo step). Rodar localmente primeiro para garantir verde (a suíte passa hoje fora da esteira — confirmar). Se houver vermelho pré-existente, triagem antes do wire (mesmo protocolo da 6A.1 passo 4).
|
||||
|
||||
**Acceptance:** PR→main roda as DUAS suítes; o claim do CLAUDE.md ("both must be green") vira verdade mecânica.
|
||||
|
||||
### Task 6A.3 — Stale-allowlist enforcement em todos os gates (suppression hygiene)
|
||||
|
||||
**O endurecimento sistêmico nº1.** Padrão validado (ESLint `--report-unused-disable-directives`; Notion): exclusão que não exclui nada vivo é dívida fantasma e furo de regressão.
|
||||
|
||||
**Files:** todos os gates com allowlist + seus testes:
|
||||
`check-fetch-targets` (KNOWN_MISSING, 7) · `check-provider-consistency` (KNOWN_REGISTRY_ONLY, 1) · `check-openapi-routes` (KNOWN_STALE_SPEC, 1) · `check-public-creds` (KNOWN_LITERAL_CREDS, 5) · `check-db-rules` (KNOWN_UNEXPORTED 25 + KNOWN_RAW_SQL 15) · `check-docs-symbols` (KNOWN_STALE_DOC_REFS, 30) · `check-migration-numbering` (KNOWN_GAPS/DUPLICATES) · `check-error-helper` (KNOWN_MISSING_ERROR_HELPER, 7 — promover o WARN existente a FAIL e cobrir também "arquivo existe mas não viola mais") · `check-deps` (dependency-allowlist: entrada sem dep correspondente em manifest algum = stale) · `check-file-size` (entrada `frozen` cujo arquivo foi deletado/renomeado) · `check-route-guard-membership` (KNOWN_UNCLASSIFIED — vazio hoje; implementar o check para quando deixar de ser).
|
||||
|
||||
**Approach (mecânica única, TDD por gate):** após a detecção normal, re-avaliar cada entrada da allowlist: *"se esta entrada não existisse, o gate flagaria algo?"* — para allowlists de path/valor isso é `violationsDetectadas.has(entry)`; para arquivos, existência + violação presente. Entrada que não suprime nada → **exit 1** com mensagem `entrada obsoleta — a violação foi corrigida; REMOVA a entrada para travar a correção`. Extrair helper comum `reportStaleEntries(allowlist, liveViolations, gateName)` em `scripts/check/lib/allowlist.mjs` para não duplicar 11×.
|
||||
|
||||
**Acceptance:** corrigir qualquer violação congelada (ex.: as issues #3483–#3501) passa a EXIGIR a remoção da entrada no mesmo PR; teste sintético prova fail-on-stale em cada gate.
|
||||
|
||||
### Task 6A.4 — Documentar os gates no CLAUDE.md (+ corrigir drifts de doc)
|
||||
|
||||
**Files:** `CLAUDE.md`, `AGENTS.md` (se houver seção espelho), `docs/architecture/` (página `QUALITY_GATES.md` referenciada pela tabela de docs).
|
||||
|
||||
**Approach:** (1) seção nova "Quality Gates & Ratchets" no CLAUDE.md: tabela dos gates (nome → o que trava → allowlist/baseline), a política **"corrija a causa; allowlist só com justificativa + issue"**, como apertar (`npm run quality:ratchet -- --update`, `check:<gate> -- --update`), e o que fazer quando um gate falha num PR. (2) Corrigir: Hard Rule #9 (60% → "catraca de cobertura: nunca abaixo do baseline congelado em `quality-baseline.json`; piso absoluto 60"), claim do pre-push (refletir o real pós-6A.12), claim "both runners" (verdade pós-6A.2). (3) Página `docs/architecture/QUALITY_GATES.md` com o detalhe operacional (o CLAUDE.md fica curto, linka). Rodar `check:docs-all` após editar (o próprio docs-sync valida).
|
||||
|
||||
**Acceptance:** agente novo lendo o CLAUDE.md descobre os gates e a política sem ler scripts; `check:docs-all` verde.
|
||||
|
||||
## P1 — endurecimento do motor + escopos
|
||||
|
||||
### Task 6A.5 — Motor v2: `--require-tighten`, eps por métrica, métricas órfãs
|
||||
|
||||
**Files:** `scripts/quality/check-quality-ratchet.mjs`, `quality-baseline.json` (schema), `tests/unit/quality-ratchet.test.ts`, `.github/workflows/ci.yml` (flag no job quality-gate).
|
||||
|
||||
**Approach (TDD):**
|
||||
1. Schema por métrica ganha campos opcionais: `eps` (default 0.01) e `tightenSlack` (default: igual a `eps`).
|
||||
2. Novo modo `--require-tighten` (ligado no CI): se `atual` melhor que `baseline` além de `tightenSlack`, **exit 1** com `melhorou de X para Y — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado neste PR`. Métricas determinísticas (`eslintWarnings`) usam slack 0; cobertura usa slack 1.5 (flutuação v8). É o "auto-decrement" da Notion adaptado a CI sem bot de commit.
|
||||
3. Warning para métricas presentes em `quality-metrics.json` sem entrada no baseline (coletor órfão).
|
||||
4. Calibrar os 4 `coverage.*` para o real medido (fecha D6 — coordenar com a 6A.1 passo 5, que muda a base).
|
||||
|
||||
**Acceptance:** melhoria sem aperto de baseline falha no CI; flutuação de coverage dentro do slack não falha; testes cobrem os 3 comportamentos novos.
|
||||
|
||||
### Task 6A.6 — `quality.yml`: gates rápidos em PRs → `release/**` ⚠️ DECISÃO DO OWNER
|
||||
|
||||
**Contexto sensível:** o owner já reverteu mudança de trigger no `ci.yml` ("não mexe na CI"). Esta task **não toca o `ci.yml`** — cria um workflow NOVO e enxuto. Ainda assim, **passo 0 = confirmação explícita do owner**.
|
||||
|
||||
**Files:** Create: `.github/workflows/quality.yml`.
|
||||
|
||||
**Approach:** `on: pull_request: branches: ["release/**"]`; um job único (~1–2 min) só com os gates determinísticos filesystem-only: provider-consistency, fetch-targets, openapi-routes, docs-symbols, deps, file-size, error-helper, migration-numbering, public-creds, db-rules, known-symbols, route-guard-membership, test-discovery (pós-6A.1) + `check:any-budget:t11`. SEM lint/test/build (continuam só no PR→main). Ganho: a violação aparece no PR que a introduz, não semanas depois no gate do release (padrão observado: 4 fixes de última hora no release da v3.8.18).
|
||||
|
||||
**Acceptance:** PR de teste contra a release branch com uma rota inventada falha em <2 min; PRs limpos não ganham mais que ~2 min de CI.
|
||||
|
||||
### Task 6A.7 — `check-fetch-targets` v2: escopo completo + prefixo de template + método HTTP
|
||||
|
||||
**Files:** `scripts/check/check-fetch-targets.mjs`, `tests/unit/check-fetch-targets.test.ts`.
|
||||
|
||||
**Approach (TDD):**
|
||||
1. **Escopo:** varrer todo `src/**/*.{ts,tsx}` client-side (excluindo `src/app/api/**`, testes, `src/lib/db`), não só `(dashboard)` — congela os misses pré-existentes que aparecerem em `KNOWN_MISSING` (com triagem/issue por cluster, igual Fase 2).
|
||||
2. **Template literals:** extrair o prefixo estático de `` fetch(`/api/x/y/${id}…`) `` e validar por **prefix-match** contra as rotas reais (existe alguma rota cujo path começa com `/api/x/y/`?). Pega diretório inteiro alucinado; não tenta resolver o sufixo dinâmico.
|
||||
3. **Método HTTP (heurístico, mesma chamada):** quando o 2º argumento literal contém `method: "POST"` (etc.), verificar que o `route.ts` resolvido exporta a função correspondente (`grep` por `export (async )?function POST` / `export const POST`). Sem method literal → assume GET-ok (rota existe basta). Casos dinâmicos → skip silencioso.
|
||||
|
||||
**Acceptance:** fixture com fetch em `src/shared/components` + template com prefixo falso + `method: "DELETE"` para rota só-GET — 3 detecções; repo real verde com os novos congelados documentados.
|
||||
|
||||
### Task 6A.8 — Escopo dos gates de segurança: error-helper, public-creds, route-guard, deps
|
||||
|
||||
**Files:** `check-error-helper.mjs`, `check-public-creds.mjs`, `check-route-guard-membership.ts`, `check-deps.mjs` + testes.
|
||||
|
||||
**Approach (TDD, um sub-commit por gate):**
|
||||
1. **error-helper** (+Rule #12 completa): incluir `open-sse/mcp-server/**` e `src/app/api/**/route.ts` no SCAN_DIRS; rodar; congelar os achados novos em KNOWN com comentário-justificativa cada (e issue por cluster).
|
||||
2. **public-creds**: além dos 2 arquivos âncora, varrer `open-sse/**` e `src/lib/oauth/**` com a mesma `CRED_KEY_RE` (linha a linha, barato); congelar achados. Limitação documentada: `const CLIENT_ID = "…"` (variável solta) continua fora — o gitleaks da Fase 7 cobre essa classe.
|
||||
3. **route-guard**: novo sub-check — todo `route.ts` (qualquer raiz) cujo fonte OU imports de 1º nível relativos contenham `child_process`/`spawn(`/`execFile(`/`worker_threads` deve ser classificado local-only por `isLocalOnlyPath()`. Mata a dependência da lista manual de 3 raízes.
|
||||
4. **deps**: `MANIFESTS` → descoberta automática de todo `package.json` do repo (fora `node_modules`/`.next`): hoje raiz, `electron/`, `open-sse/`, `@omniroute/opencode-plugin/` (dep `zod` entra na allowlist), `@omniroute/opencode-provider/`. Workspace novo amanhã entra sozinho.
|
||||
|
||||
**Acceptance:** fixtures sintéticas por gate; repo real verde com achados congelados + issues; dep nova em QUALQUER manifest do repo dispara o gate.
|
||||
|
||||
### Task 6A.9 — `check-known-symbols` v2: MCP tools, A2A skills, cloud agents
|
||||
|
||||
**Files:** `scripts/check/check-known-symbols.ts`, `tests/unit/check-known-symbols.test.ts`.
|
||||
|
||||
**Approach (TDD, mesmo padrão das 3 superfícies existentes — Step 0 de verificação dos exports reais antes de codar):**
|
||||
1. **MCP tools:** enumerar os tools registrados (via `createMcpServer()` ou parse determinístico do tool-set em `open-sse/mcp-server/tools/`) e congelar o snapshot de nomes (catraca: tool sumir = fail; tool novo = report). Cruzar com os scopes (~13) — tool sem scope atribuído = fail.
|
||||
2. **A2A skills:** chaves de `A2A_SKILL_HANDLERS` (`src/lib/a2a/taskExecution.ts`) ↔ skills expostas no Agent Card (`src/app/.well-known/agent.json/route.ts`) — divergência = fail.
|
||||
3. **Cloud agents:** entradas do `src/lib/cloudAgent/registry.ts` ↔ classes em `agents/` — incompleto/órfão = fail.
|
||||
|
||||
**Acceptance:** remover um tool/skill/agent do registro quebra o gate; adicionar reporta (e `check-docs-counts-sync` continua cuidando da prosa).
|
||||
|
||||
## P2 — refinamentos
|
||||
|
||||
### Task 6A.10 — `check-test-masking` v2: deleções, skips, tautologias
|
||||
|
||||
**Files:** `scripts/check/check-test-masking.mjs`, `tests/unit/check-test-masking.test.ts`.
|
||||
|
||||
**Approach (TDD):** (a) `--diff-filter=M` → `MDR` (com `-M` para rename-detection): arquivo de teste **deletado** = flag automático ("N asserts removidos — arquivo deletado"); renamed = comparar contra o path antigo. (b) Contar `\.(skip|todo|only)\s*\(` + `\{\s*skip:\s*true` base vs HEAD — **aumento líquido de skips = flag** (skip novo esconde asserts sem removê-los); `.only` novo = flag sempre (filtra o resto da suíte). (c) Tautologias extras: `expect(true).toBe(true)`, `assert.equal(1, 1)`, `expect(x).toBeDefined()` como ÚNICO assert do teste.
|
||||
|
||||
**Acceptance:** fixtures para os 3 bypasses (delete, skip, only) — todos flagados; suíte real verde.
|
||||
|
||||
### Task 6A.11 — Pisos manuais → catraca do motor + escopo electron/bin
|
||||
|
||||
**Files:** `scripts/quality/collect-metrics.mjs`, `quality-baseline.json`, `check-openapi-coverage.mjs`, `scripts/i18n/check-ui-keys-coverage.mjs` (só leitura do valor), `check-file-size.mjs`, `eslint.complexity.config.mjs` + baselines.
|
||||
|
||||
**Approach:** (1) coletor emite `openapiCoverage.pct` e `i18nUiCoverage.pct` → baseline `{direction: up}` com o valor real atual (36→real, 65→real); os THRESHOLDs fixos viram redundância de segurança (mantidos). Rotas/strings novas sem doc/i18n agora REGRIDEM o % e falham. (2) `check-file-size` e `check-complexity`: adicionar `electron/` e `bin/` ao scan (congelar os >cap que existirem).
|
||||
|
||||
**Acceptance:** rota nova não-documentada derruba `openapiCoverage.pct` → gate falha; god-file novo em `electron/` falha.
|
||||
|
||||
### Task 6A.12 — Higiene operacional (4 itens pequenos)
|
||||
|
||||
**Files:** `package.json`, `dependency-allowlist.json`, `.husky/pre-push`, Create: `scripts/check/check-tracked-artifacts.mjs`, `scripts/quality/run-all-gates.mjs`; Modify: `.agents/skills/quality-scan/SKILL.md`.
|
||||
|
||||
**Approach:**
|
||||
1. **jscpd pinado:** `jscpd@^4` como devDependency (entra no lockfile + allowlist); `check-duplication` chama o binário local em vez de `npx --yes jscpd@4` (remove download de registry no CI + supply-chain risk + flakiness).
|
||||
2. **pre-push barato:** reativar com APENAS os gates determinísticos rápidos (<10s: fetch-targets, openapi-routes, db-rules, public-creds, migration-numbering, file-size, deps, error-helper) — NÃO `test:unit` (lento; CI cobre). Atualizar o claim do CLAUDE.md (coordenar com 6A.4).
|
||||
3. **check-tracked-artifacts:** falhar se `git ls-files` contém `node_modules/`, `.next/`, `coverage/`, `quality-metrics.json` ou symlink para fora do repo (o incidente do symlink trackeado já aconteceu 2×). Wire no lint job + pre-commit (é instantâneo).
|
||||
4. **Runner agregador:** `scripts/quality/run-all-gates.mjs` roda os gates em paralelo (pool ~4), agrega `{gate, exitCode, lastLine, durationMs}` e imprime a tabela consolidada; `npm run quality:scan`. A skill `/quality-scan` passa a chamá-lo (atualizar SKILL.md).
|
||||
|
||||
**Acceptance:** `npm run quality:scan` < 3 min com tabela única; pre-push roda <10s; `git add node_modules && commit` falha no pre-commit.
|
||||
|
||||
---
|
||||
|
||||
## Ordem de execução recomendada (na ativação, 2026-06-16+)
|
||||
|
||||
1. **6A.1 + 6A.2** (bugs de runner — destravam números reais de cobertura para o resto)
|
||||
2. **6A.3 + 6A.4** (stale-enforcement + docs — endurecem o que já roda)
|
||||
3. **6A.5** (motor v2) → **6A.6** (quality.yml, após OK do owner)
|
||||
4. **6A.7 → 6A.9** (escopos)
|
||||
5. **6A.10 → 6A.12** (refinamentos)
|
||||
6. Só então **Fase 7** (ferramentas novas sobre uma fundação consertada)
|
||||
|
||||
## Self-Review
|
||||
- **Cobertura dos achados:** todos os achados A*/B*/C*/D*/E* da tabela têm task (coluna Task); os descartados estão em "Decisões conscientes de NÃO fazer" com motivo. ✓
|
||||
- **Zero dependência nova** exceto a promoção do jscpd (que já roda hoje via npx, não-pinado — a task REDUZ risco). ✓
|
||||
- **Sem flag-day:** toda expansão de escopo congela os achados pré-existentes (allowlist + issue), igual Fases 0–6; o stale-enforcement só exige remoção quando a correção JÁ aconteceu. ✓
|
||||
- **Consistência com o motor:** novas métricas (`openapiCoverage.pct`, `i18nUiCoverage.pct`) usam o formato `{value, direction}`; `eps`/`tightenSlack` são opcionais e retrocompatíveis. ✓
|
||||
- **Não-duplicação com a Fase 7:** cognitive-complexity per-file, gitleaks (creds não-públicas), knip, osv — tudo continua na Fase 7; a 6A só conserta/endurece o existente. ✓
|
||||
@@ -1,182 +0,0 @@
|
||||
# Fase 7 — Quality Gates: Segurança, Dead-Code, Mutação & Ferramental Community
|
||||
|
||||
> **Para workers agênticos:** SUB-SKILL OBRIGATÓRIA: `superpowers:subagent-driven-development` (recomendado) ou `superpowers:executing-plans`, tarefa-a-tarefa. Cada tarefa aqui é um subsistema independente → **expandir em sub-plano bite-sized próprio** no momento da execução. Hard Rule #18 (TDD/VPS) em tudo.
|
||||
|
||||
> # ⏳ PORTÃO DE ATIVAÇÃO — NÃO INICIAR ANTES DE **2026-06-16**
|
||||
> **Este plano está GUARDADO, não ativo.** Decisão do owner (2026-06-09): finalizar 100% as Fases 0–6 (PR #3471), **usar em produção por 1 semana** para validar na prática, e só então evoluir. **Data cravada de início da Fase 7: 2026-06-16.** Não ativar antes — o objetivo da semana é coletar sinal real (falsos-positivos dos gates, custo de CI, atrito) antes de adicionar mais.
|
||||
> **Pré-condições para ativar:** (1) PR #3471 (Fases 0–6) mergeada e rodada ≥1 semana; (2) re-home do PR para `release/v3.8.18` resolvido; (3) as issues #3483–#3501 com decisões aplicadas ou conscientemente adiadas; (4) **Fase 6A executada (ou conscientemente re-priorizada)** — a auditoria crítica de 2026-06-09 ([`PLANO-QUALITY-GATES-FASE6A.md`](./PLANO-QUALITY-GATES-FASE6A.md)) encontrou bugs sistêmicos de runner (≈135 testes órfãos, vitest fora do CI) e furos de escopo nos gates existentes que devem ser consertados ANTES de adicionar ferramentas novas por cima.
|
||||
|
||||
**Goal:** Maximizar a cobertura de quality gates do OmniRoute adicionando catracas de **segurança** (Sonar/osv/CodeQL → zero na timeline), **dead-code**, **complexidade cognitiva**, **type-coverage**, **mutação**, **bundle-size**, **a11y** e completando o anti-slopsquatting — usando **somente ferramentas Community/OSS** (projeto é open-source, zero SaaS pago, dados na box).
|
||||
|
||||
**Architecture:** Reusa o motor existente — toda métrica numérica entra como `{value, direction}` em `quality-baseline.json` (catraca só-regressão) ou vira um `scripts/check/check-*.mjs` dedicado (padrão `check-t11-any-budget.mjs`). Gates pesados vão no job paralelo `quality-gate`; gates rápidos no `lint`; mutação/visual em job nightly separado. Tudo só-regressão (sem flag-day).
|
||||
|
||||
**Tech Stack (tudo OSS/Community):** SonarQube **Community Build** (self-hosted) · osv-scanner (Google) · CodeQL (GitHub, grátis p/ público) · knip · eslint-plugin-sonarjs · type-coverage · lockfile-lint · dpdm · Stryker (`@stryker-mutator/*`) · size-limit · `@axe-core/playwright` · semcheck · agent-lsp (MCP) · Qlty CLI (OSS, opcional) · **gitleaks** (secret scanning, MIT; avaliar o sucessor drop-in Betterleaks, 2026-03) · **actionlint + zizmor** (lint + auditoria de segurança dos workflows) · **license-compliance** (allowlist SPDX de licenças). ESLint 9 flat · c8 · Node native test runner · GitHub Actions.
|
||||
|
||||
---
|
||||
|
||||
## Princípio (igual às Fases 0–6)
|
||||
|
||||
Toda catraca é **só-regressão**: congela o baseline atual, bloqueia QUALQUER piora, decai a zero/melhor com o tempo via `--update`. Nenhum gate exige limpeza imediata (flag-day). Cada ferramenta nova que vira dependência **deve ser adicionada a `dependency-allowlist.json`** (o gate `check-deps` da Fase 2 vai exigir — é o ponto de revisão humana).
|
||||
|
||||
## Mapa de arquivos (criar/modificar)
|
||||
|
||||
| Arquivo | Responsabilidade |
|
||||
|---|---|
|
||||
| `quality-baseline.json` (modificar) | + `vulnCount`, `codeqlAlerts`, `sonarIssues`, `cognitiveComplexity`, `typeCoveragePct`, `deadExports` |
|
||||
| `scripts/quality/collect-metrics.mjs` (modificar) | + coletores: osv-scanner, CodeQL count, Sonar API, knip, type-coverage, sonarjs |
|
||||
| `scripts/check/check-vuln-ratchet.mjs` (criar) | osv-scanner → vulnCount (catraca) |
|
||||
| `scripts/check/check-dead-code.mjs` (criar) | knip → exports/files/deps mortos (catraca) |
|
||||
| `scripts/check/check-cognitive-complexity.mjs` + `eslint.sonarjs.config.mjs` (criar) | sonarjs/cognitive-complexity em config isolado (não polui o count principal) |
|
||||
| `scripts/check/check-type-coverage.mjs` (criar) | type-coverage % (catraca up) |
|
||||
| `scripts/check/check-lockfile.mjs` (criar) | lockfile-lint (host/https/integrity) |
|
||||
| `scripts/check/check-pr-evidence.mjs` (criar) | exige output de comando no corpo do PR (Rule #18 mecânica) |
|
||||
| `scripts/check/check-bundle-size.mjs` + `.size-limit.json` (criar) | size-limit → orçamento de bundle |
|
||||
| `tests/e2e/a11y.spec.ts` (criar) | `@axe-core/playwright` nas páginas-chave |
|
||||
| `stryker.conf.json` (criar) | mutação nos ~8 módulos críticos (nightly) |
|
||||
| `sonar-project.properties` (modificar) | remover `coverage`/`cpd` exclusions; ativar new-code gate |
|
||||
| `.github/workflows/ci.yml` (modificar) | wirar novos gates (lint / quality-gate / nightly) + `qualitygate.wait` no Sonar |
|
||||
| `semcheck.yaml` (criar) | semcheck: docs↔código (camada fuzzy LLM, opcional) |
|
||||
| `.mcp.json` / config de agentes (modificar) | registrar agent-lsp (LSP-in-the-loop) |
|
||||
| `.gitleaks.toml` + `scripts/check/check-secrets.mjs` (criar) | gitleaks → catraca de findings de secret (Task 18) |
|
||||
| `.github/workflows/quality.yml` ou job lint (modificar) | actionlint + zizmor sobre `.github/workflows/**` (Task 19) |
|
||||
| `scripts/check/check-licenses.mjs` + `.license-allowlist.json` (criar) | allowlist SPDX das licenças das deps (Task 20) |
|
||||
| `dependency-allowlist.json` (modificar) | + osv-scanner, knip, sonarjs, type-coverage, lockfile-lint, stryker, size-limit, axe-core, dpdm, license-compliance |
|
||||
|
||||
---
|
||||
|
||||
## Tarefas (cada uma = 1 sub-plano bite-sized na execução)
|
||||
|
||||
### Task 1 — Ativar SonarQube Community + "Clean as You Code" (gate de segurança nativo)
|
||||
- **Tool:** SonarQube Community Build (self-hosted, grátis).
|
||||
- **Files:** `sonar-project.properties`, `.github/workflows/ci.yml` (job `sonarqube`).
|
||||
- **Approach:** setar secrets `SONAR_TOKEN`/`SONAR_HOST_URL`; **remover** `sonar.coverage.exclusions=**/*` e `sonar.cpd.exclusions=**/*` (hoje neutralizam o Sonar); ativar o quality gate **new-code / "Clean as You Code"** (código novo não pode adicionar issue/bug/vuln/hotspot; legado grandfathered) + adicionar `-Dsonar.qualitygate.wait=true` para **bloquear** o PR (hoje o job é inerte: secrets-gated, sem wait).
|
||||
- **Acceptance:** PR que introduz um code-smell/bug/vuln em código novo falha o gate; legado não bloqueia. Documentar suppressions legítimas (já há h1–h6 no properties).
|
||||
|
||||
### Task 2 — Catraca de vulnerabilidades (osv-scanner)
|
||||
- **Tool:** osv-scanner (Google/OSV, OSS) — `--format json`, on-box.
|
||||
- **Files:** `scripts/check/check-vuln-ratchet.mjs`, `quality-baseline.json` (+`vulnCount`), `collect-metrics.mjs`, `ci.yml` (job `quality-gate`), `dependency-allowlist.json`.
|
||||
- **Approach:** rodar `osv-scanner --format json` sobre os lockfiles → contar vulns → métrica `vulnCount {direction: down}`. Catraca: não pode subir, decai a zero. Mantém o `npm audit` escalonado da Fase 0 como bloqueio de crítico imediato; osv é o ratchet de timeline.
|
||||
- **Acceptance:** nova dep com vuln conhecida sobe o count → falha; remediar/remover baixa → `--update`.
|
||||
|
||||
### Task 3 — Catraca de alertas CodeQL
|
||||
- **Tool:** GitHub CodeQL (já roda; grátis p/ repo público).
|
||||
- **Files:** `scripts/check/check-codeql-ratchet.mjs`, `quality-baseline.json` (+`codeqlAlerts`), `ci.yml`.
|
||||
- **Approach:** puxar a contagem de alertas abertos via `gh api /repos/{owner}/{repo}/code-scanning/alerts?state=open` → métrica `codeqlAlerts {down}`. (Respeitar Hard Rule #14 — dismiss só com justificativa; alertas dismissed não contam.)
|
||||
- **Acceptance:** novo alerta CodeQL sobe o count → sinaliza; resolver baixa.
|
||||
|
||||
### Task 4 — Dead-code / unused-exports / unused-deps (knip)
|
||||
- **Tool:** knip (OSS, v6+) — `--reporter json`.
|
||||
- **Files:** `scripts/check/check-dead-code.mjs`, `quality-baseline.json` (+`deadExports`/`unusedDeps`), `knip.json` (config), `collect-metrics.mjs`, `ci.yml`, `dependency-allowlist.json`.
|
||||
- **Approach:** `knip --reporter json` sobre os workspaces `src/`+`open-sse/` → contar unused files/exports/deps → catraca `down`. Config knip ciente do monorepo + Next 16.
|
||||
- **Acceptance:** novo export/dep morto sobe → falha; remoção baixa.
|
||||
|
||||
### Task 5 — Complexidade cognitiva (eslint-plugin-sonarjs, config isolado)
|
||||
- **Tool:** eslint-plugin-sonarjs (OSS) — `sonarjs/cognitive-complexity`.
|
||||
- **Files:** `eslint.sonarjs.config.mjs` (config standalone, NÃO o principal — não polui o `eslintWarnings=3482`), `scripts/check/check-cognitive-complexity.mjs`, `quality-baseline.json` (+`cognitiveComplexity`), `ci.yml` (job `quality-gate`), `dependency-allowlist.json`.
|
||||
- **Approach:** mesmo molde do `check-complexity` (Fase 6) mas com `sonarjs/cognitive-complexity` num config isolado; contar violações → catraca `down`. (Complementa a complexidade ciclomática core já existente.)
|
||||
- **Acceptance:** função acima do limite cognitivo sobe o count → falha.
|
||||
|
||||
### Task 6 — Type-coverage ratchet
|
||||
- **Tool:** type-coverage (OSS).
|
||||
- **Files:** `scripts/check/check-type-coverage.mjs`, `quality-baseline.json` (+`typeCoveragePct {up}`), `dependency-allowlist.json`.
|
||||
- **Approach:** `type-coverage --detail --json` → % de símbolos tipados → catraca `up`. Complementa o `check:any-budget` (count de `any` por arquivo) com a visão %-global.
|
||||
- **Acceptance:** queda do % tipado → falha.
|
||||
|
||||
### Task 7 — Lockfile policy (lockfile-lint)
|
||||
- **Tool:** lockfile-lint (OSS, v5).
|
||||
- **Files:** `scripts/check/check-lockfile.mjs`, `ci.yml` (lint), `dependency-allowlist.json`.
|
||||
- **Approach:** `lockfile-lint --path package-lock.json --type npm --validate-https --validate-integrity --allowed-hosts npm` → gate pass/fail (não é ratchet; é política anti-poisoning). Complementa o `check-deps` (Fase 2).
|
||||
- **Acceptance:** lockfile com host não-https/sem integrity → falha.
|
||||
|
||||
### Task 8 — Completar anti-slopsquatting (registry-existence + age-cooldown)
|
||||
- **Tool:** npm registry API (`npm view <pkg> time.created`).
|
||||
- **Files:** `scripts/check/check-deps.mjs` (estender), test.
|
||||
- **Approach:** além do allowlist-diff atual, para uma dep NOVA: verificar que existe no registry E que foi publicada há ≥72h (age-cooldown contra "registra o nome alucinado em horas"). Base: CSA 2026 (19,7% de nomes alucinados; 43% reaparecem).
|
||||
- **Acceptance:** dep nova inexistente no registry ou publicada há <72h → falha (a menos que allowlistada com justificativa).
|
||||
|
||||
### Task 9 — Pisos de cobertura por módulo crítico (peça adiada da Fase 4)
|
||||
- **Files:** `scripts/quality/collect-metrics.mjs` (estender), `quality-baseline.json`.
|
||||
- **Approach:** emitir `coverage.<modulo>.lines` (lido do `coverage-summary.json` por-arquivo) para ~8 módulos de alto risco: `open-sse/handlers/chatCore.ts`, `open-sse/services/combo.ts`, `open-sse/services/accountFallback.ts`, `src/sse/services/auth.ts`, `src/server/authz/routeGuard.ts`, `open-sse/utils/error.ts`, `open-sse/utils/publicCreds.ts`, `src/shared/utils/circuitBreaker.ts`. Cada um vira métrica `up`. **Calibrar a partir do coverage mergeado real do 1º run verde na main.**
|
||||
- **Acceptance:** queda de cobertura num módulo crítico → falha, mesmo que o global não caia.
|
||||
|
||||
### Task 10 — Evidence-in-PR-body (peça adiada da Fase 5)
|
||||
- **Files:** `scripts/check/check-pr-evidence.mjs`, `ci.yml` (job `pr-test-policy`).
|
||||
- **Approach:** se o corpo do PR afirma "tests pass"/"added endpoint X"/"fixed Y" sem um bloco de **output de comando** anexado (typecheck/test/grep), falha (torna a Rule #18 mecânica — "evidence before assertions"). Heurístico, no contexto de PR.
|
||||
- **Acceptance:** PR alegando sucesso sem output anexado → falha.
|
||||
|
||||
### Task 11 — Mutation testing nos módulos críticos (Stryker, nightly)
|
||||
- **Tool:** Stryker (`@stryker-mutator/core` + runner; OSS).
|
||||
- **Files:** `stryker.conf.json`, `ci.yml` (job NIGHTLY separado — não no PR), `dependency-allowlist.json`.
|
||||
- **Approach:** escopar Stryker aos ~8 módulos críticos da Task 9 (não repo-wide — é caro + c8 já OOM-prone). Mutantes sobreviventes = **testes tautológicos** (passam sem provar nada) → complementa o `check-test-masking` da Fase 4. Rodar nightly/weekly, não por-PR.
|
||||
- **Acceptance:** mutation score por módulo crítico vira métrica (catraca `up`, nightly).
|
||||
|
||||
### Task 12 — Bundle-size / perf budget (size-limit)
|
||||
- **Tool:** size-limit (OSS).
|
||||
- **Files:** `.size-limit.json`, `scripts/check/check-bundle-size.mjs`, `ci.yml`, `dependency-allowlist.json`.
|
||||
- **Approach:** definir orçamento por bundle Next 16; size-limit emite tamanhos → catraca `down` (bundle não pode inchar).
|
||||
- **Acceptance:** PR que estoura o orçamento de bundle → falha.
|
||||
|
||||
### Task 13 — a11y gate (axe-core + Playwright)
|
||||
- **Tool:** `@axe-core/playwright` (OSS; Playwright já existe).
|
||||
- **Files:** `tests/e2e/a11y.spec.ts`, `ci.yml` (job `test-e2e`), `dependency-allowlist.json`.
|
||||
- **Approach:** rodar axe nas páginas-chave do dashboard; congelar violações atuais (catraca `down`). Atende o item a11y/visual do plano t15.
|
||||
- **Acceptance:** nova violação a11y → falha; correção baixa.
|
||||
|
||||
### Task 14 — semcheck (camada fuzzy docs↔código, opcional/LLM)
|
||||
- **Tool:** semcheck (OSS, MIT) — `fail-on-issues`.
|
||||
- **Files:** `semcheck.yaml`, `ci.yml` (advisory).
|
||||
- **Approach:** regras ligando `docs/**` ao módulo de código que documentam; pega docs que descrevem o que o código NÃO faz (camada fuzzy sobre o determinístico `check-docs-symbols` da Fase 6). É LLM → rodar advisory/non-blocking ou em label, por custo.
|
||||
- **Acceptance:** doc que descreve comportamento inexistente → flag (advisory).
|
||||
|
||||
### Task 15 — agent-lsp (LSP-in-the-loop para os agentes)
|
||||
- **Tool:** agent-lsp (MCP server, OSS).
|
||||
- **Files:** config MCP dos agentes (`.mcp.json`/equivalente).
|
||||
- **Approach:** expor `tsserver`/agent-lsp aos agentes para `blast_radius`/diagnostics/`preview_edit` ANTES de escrever — vira "símbolo inventado" de catch-de-review para impossibilidade-no-edit. Pareia com `typecheck:core` como gate pré-PR (compile-before-claim).
|
||||
- **Acceptance:** agentes resolvem símbolo/import via LSP; menos alucinação de símbolo na origem.
|
||||
|
||||
### Task 16 — dpdm circular-deps JSON cross-check (opcional)
|
||||
- **Tool:** dpdm (OSS, v4) — `--circular --output`.
|
||||
- **Approach:** cross-check JSON de ciclos complementando o `check-cycles.mjs` existente (AST-TS mais preciso). Catraca de contagem de ciclos. Baixa prioridade (já temos check-cycles).
|
||||
|
||||
### Task 17 — Avaliar Qlty CLI como consolidador (opcional, spike)
|
||||
- **Tool:** Qlty CLI (OSS, grátis).
|
||||
- **Approach:** spike: avaliar se Qlty (Baseline analysis + 70 analyzers) substitui N scripts caseiros sem perder o controle/determinismo. Decisão build-vs-buy. Não obrigatório.
|
||||
|
||||
### Task 18 — Secret scanning local (gitleaks) ➕ *adicionada pela auditoria 6A (2026-06-09)*
|
||||
- **Tool:** gitleaks (OSS, MIT — binário Go, on-box). Nota 2026: o criador original (Zach Rice) lançou o **Betterleaks** (2026-03) como drop-in replacement (flags/config compatíveis) — avaliar os dois no Step 0 e escolher 1.
|
||||
- **Files:** `.gitleaks.toml`, `scripts/check/check-secrets.mjs`, `quality-baseline.json` (+`secretFindings {down}`), `ci.yml` (job quality-gate), pre-commit (modo `--staged`, é rápido).
|
||||
- **Approach:** complementa o `check-public-creds` da Fase 6 (que cobre apenas credenciais PÚBLICAS por chave de objeto em 2 arquivos): gitleaks pega a classe geral — `const API_KEY = "sk-…"`, tokens em config/teste/docs, secrets em histórico. Rodar `gitleaks dir --report-format json` → contar findings → catraca `down`. Findings legítimos (creds públicas já congeladas no check-public-creds, fixtures de teste) vão para `.gitleaks.toml` `[allowlist]` com comentário — sujeitos ao stale-enforcement da 6A.3 (conceitual: revisar allowlist a cada release).
|
||||
- **Acceptance:** secret real plantado em fixture é detectado; baseline congela os findings atuais; novo finding falha o gate.
|
||||
|
||||
### Task 19 — Lint + auditoria de segurança dos workflows (actionlint + zizmor) ➕ *adicionada pela auditoria 6A*
|
||||
- **Tools:** actionlint (OSS — correção/sintaxe/shellcheck dos YAML) + zizmor (OSS, zizmorcore — 24+ audits de segurança: unpinned actions, script injection, `pull_request_target` perigoso, cache poisoning). Complementares por design; o repo tem 10 workflows sem NENHUMA validação hoje.
|
||||
- **Motivação 2026:** o incidente trivy-action/LiteLLM (2026-03) explorou exatamente uma misconfiguração de `pull_request_target` que o zizmor detecta estaticamente. Os release-workflows do OmniRoute (npm/Docker/Electron publish) são alvo de alto valor.
|
||||
- **Files:** `ci.yml` ou `quality.yml` (steps actionlint + zizmor), `zizmor.yml` (config/ignores justificados), `quality-baseline.json` (+`zizmorFindings {down}` — começar advisory, congelar baseline, depois bloquear).
|
||||
- **Approach:** actionlint = pass/fail imediato (sintaxe não tem "legado aceitável"); zizmor = catraca `down` no padrão do motor (os findings atuais — provavelmente actions não-pinadas por SHA — são dívida congelada que decai).
|
||||
- **Acceptance:** workflow novo com `pull_request_target` + checkout de código do PR falha; action não-pinada NOVA sobe o count e falha.
|
||||
|
||||
### Task 20 — License compliance (allowlist SPDX) ➕ *adicionada pela auditoria 6A*
|
||||
- **Tool:** license-compliance ou @onebeyond/license-checker (ambos OSS, npm). Projeto é **MIT** — deps com copyleft forte (GPL/AGPL) em produção são risco de compliance para os usuários do proxy.
|
||||
- **Files:** `scripts/check/check-licenses.mjs`, `.license-allowlist.json` (SPDX permitidas: MIT, Apache-2.0, BSD-2/3, ISC, 0BSD, …), `ci.yml` (lint job), `dependency-allowlist.json`.
|
||||
- **Approach:** rodar sobre as `dependencies` de produção (devDependencies = relatório advisory); licença fora da allowlist → fail com o caminho da dep. Exceções pontuais (dual-license, LGPL avaliada) entram na allowlist por **pacote** com justificativa — pareia com o `check-deps` da Fase 2 (lá controla O QUE entra; aqui, SOB QUAL licença).
|
||||
- **Acceptance:** dep GPL-3.0 sintética em fixture falha; árvore atual passa com a allowlist calibrada.
|
||||
|
||||
---
|
||||
|
||||
## Wiring & CI (resumo)
|
||||
- **lint job:** check-lockfile, check-cognitive-complexity (rápido?), check-type-coverage, **check-licenses (Task 20)**, **actionlint (Task 19)**.
|
||||
- **quality-gate job (paralelo):** check-vuln-ratchet, check-dead-code, check-codeql-ratchet, check-cognitive-complexity (se lento), check-bundle-size, **check-secrets (Task 18)**, **zizmor (Task 19)**.
|
||||
- **pr-test-policy job:** check-pr-evidence.
|
||||
- **sonarqube job:** Clean-as-You-Code + `qualitygate.wait`.
|
||||
- **NIGHTLY job (novo):** Stryker (mutação), semcheck (advisory), a11y full.
|
||||
- Todas as métricas numéricas → `quality-baseline.json` (motor da Fase 1, com `eps`/`tightenSlack` da 6A.5). Toda dep nova → `dependency-allowlist.json`. Toda allowlist nova nasce com o stale-enforcement da 6A.3.
|
||||
|
||||
## Self-Review
|
||||
- **Cobertura do spec:** 7 gates sugeridos = Task 1-3 (segurança), 4 (knip), 9 (coverage por módulo), 10 (evidence), 11 (mutação), 12 (bundle), 13 (a11y). "Todas as ferramentas discutidas" = Tasks 1-8, 11-17 (Sonar/osv/CodeQL/knip/sonarjs/type-coverage/lockfile/dpdm/stryker/size-limit/axe/semcheck/agent-lsp/Qlty). Auditoria 6A (2026-06-09) acrescentou Tasks 18-20 (gitleaks, actionlint+zizmor, license compliance). ✓
|
||||
- **Community/OSS only:** confirmado — Sonar Community Build, todos os demais OSS (gitleaks MIT, zizmor/actionlint OSS, license-compliance npm), zero SaaS pago. ✓
|
||||
- **Sem flag-day:** toda catraca é só-regressão, calibrada do estado atual (zizmor/gitleaks começam advisory→baseline→bloqueio). ✓
|
||||
- **Consistência:** todas as métricas usam o formato `{value, direction}` do motor da Fase 1; deps novas passam pelo `check-deps`+`dependency-allowlist.json`. ✓
|
||||
- **Não-sobreposição com a 6A:** a 6A conserta/endurece o EXISTENTE (runners, stale-allowlists, escopos); a Fase 7 só adiciona ferramenta nova. gitleaks (Task 18) complementa — não substitui — o check-public-creds expandido pela 6A.8. ✓
|
||||
|
||||
## Handoff (na ativação, 2026-06-16+)
|
||||
**Ordem: Fase 6A primeiro** ([`PLANO-QUALITY-GATES-FASE6A.md`](./PLANO-QUALITY-GATES-FASE6A.md)) — consertar os runners (testes órfãos + vitest no CI) e endurecer as catracas existentes muda os baselines (cobertura recalibrada) sobre os quais várias tasks daqui (1, 9, 11) calibram. Depois: começar pela **Task 1-3 (catraca de segurança)**, **Task 4 (knip)** e **Task 19 (zizmor — protege os release-workflows)** — maior retorno. Cada Task vira um sub-plano `writing-plans` bite-sized próprio. Recomendado: Subagent-Driven, 1 subagente por Task, com auditoria (trust-but-verify) e o ratchet `eslintWarnings`/`check-deps` validando que cada adição não regride o que já temos.
|
||||
@@ -1,688 +0,0 @@
|
||||
# Plano de Implementação — Quality Gates & Catraca Anti-Alucinação
|
||||
|
||||
> **Para workers agênticos:** SUB-SKILL OBRIGATÓRIA: use `superpowers:subagent-driven-development` (recomendado) ou `superpowers:executing-plans` para executar este plano tarefa-a-tarefa. Os passos usam checkbox (`- [ ]`) para rastreio. **Cada fix de bug obedece à Hard Rule #18** (teste falha→passa OU validação ao vivo no VPS). Não burle Husky (`--no-verify`) sem aprovação. Veja o diagnóstico completo em [`RELATORIO-QUALITY-GATES.md`](./RELATORIO-QUALITY-GATES.md).
|
||||
|
||||
**Goal:** Generalizar a catraca de qualidade do OmniRoute (hoje só para `any`) para todas as métricas relevantes — cobertura, duplicação, tamanho de arquivo, complexidade — e adicionar gates determinísticos anti-alucinação, tudo no padrão `check-*.mjs` já existente, sem SaaS novo.
|
||||
|
||||
**Architecture:** Camadas incrementais. (0) reativa/reconcilia o que já existe; (1) constrói o **motor de catraca** (`quality-baseline.json` commitado + coletor + comparador genérico que clona o `check-t11-any-budget.mjs`); (2) adiciona gates determinísticos que matam os ímãs de alucinação (provider-consistency, fetch-targets, openapi-routes); (3) catraca de duplicação+tamanho; (4) catraca de cobertura + anti test-masking; (5) skill `/babysit` + evidência. Toda catraca é **só-regressão** (baseline congelado) — nunca um piso absoluto que exija limpeza flag-day.
|
||||
|
||||
**Tech Stack:** Node ≥20 ESM (`.mjs`/`.ts` via `tsx`), ESLint 9 flat config, c8, jscpd v5, eslint-plugin-sonarjs v4, GitHub Actions, Node native test runner (`node --import tsx --test`), `gh` CLI + GraphQL.
|
||||
|
||||
**Escopo / sub-planos (scope-check):** Fases 0, 1 e 2 estão totalmente bite-sized aqui. Fases 3, 4 e 5 são subsistemas independentes — cada uma deve ser **expandida no próprio sub-plano** (via `writing-plans`) no momento da execução, a partir das specs/critérios de aceitação definidos aqui. Cada fase entrega software funcional e testável por si só.
|
||||
|
||||
---
|
||||
|
||||
## Mapa de arquivos (o que será criado/modificado)
|
||||
|
||||
| Arquivo | Responsabilidade |
|
||||
|---------|------------------|
|
||||
| `quality-baseline.json` (criar, **commitar**) | Baseline congelado: por métrica `{value, direction}` (`down`=menor-é-melhor, `up`=maior-é-melhor) |
|
||||
| `scripts/quality/collect-metrics.mjs` (criar) | Roda os coletores → emite `quality-metrics.json` |
|
||||
| `scripts/quality/check-quality-ratchet.mjs` (criar) | Comparador genérico: falha em qualquer regressão; com `--update` ratcheta o baseline |
|
||||
| `scripts/check/check-fetch-targets.mjs` (criar) | Todo `fetch("/api/...")` do dashboard resolve para um `route.ts` real |
|
||||
| `scripts/check/check-provider-consistency.ts` (criar) | ids de provider batem entre `providers.ts` ↔ `providerRegistry.ts` ↔ `validation.ts` |
|
||||
| `scripts/check/check-openapi-routes.mjs` (criar) | Toda `path` do `openapi.yaml` ↔ `route.ts` real (bidirecional) |
|
||||
| `scripts/check/check-deps.mjs` (criar) | Anti-slopsquatting: allowlist + existência no registry + age-cooldown |
|
||||
| `.github/workflows/ci.yml` (modificar) | Novo job `quality-gate`; reconciliar gate de cobertura; escalonar audit; plugar scripts órfãos |
|
||||
| `.husky/pre-commit` (modificar) | Reativar a parte barata |
|
||||
| `package.json` (modificar) | Novos scripts `check:*` / `quality:*` |
|
||||
| `eslint.config.mjs` (modificar, Fase 3) | `max-lines`, `max-lines-per-function`, `complexity`, `sonarjs/cognitive-complexity` (warn) |
|
||||
| `.claude/skills/babysit/SKILL.md` (criar, Fase 5) | Skill `/babysit` |
|
||||
| `tests/unit/quality-ratchet.test.ts` etc. (criar) | Testes TDD de cada gate |
|
||||
|
||||
---
|
||||
|
||||
# FASE 0 — Reativar & Reconciliar (quick wins, sem tooling novo)
|
||||
|
||||
### Task 0.1: Reconciliar o gate de cobertura do CI (40 → baseline real)
|
||||
|
||||
**Contexto:** `ci.yml:377` gata em `40/40/40/40`; local gata `60`; comentário renderiza `60`; baseline real ≈ 79–82%. O 40 torna o gate quase banguela.
|
||||
|
||||
**Files:**
|
||||
- Modify: `.github/workflows/ci.yml:376-377`
|
||||
|
||||
- [ ] **Step 1: Confirmar o baseline real de cobertura**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
npm run test:coverage 2>&1 | tail -20
|
||||
node -e "const c=require('./coverage/coverage-summary.json').total; console.log(c.statements.pct,c.lines.pct,c.functions.pct,c.branches.pct)"
|
||||
```
|
||||
Expected: 4 números (ex.: `79.8 79.8 82.2 75.2`). Anote-os.
|
||||
|
||||
- [ ] **Step 2: Subir o gate do CI para `baseline_real - 2` (headroom anti-flake)**
|
||||
|
||||
Em `.github/workflows/ci.yml`, troque a linha `--statements 40 --lines 40 --functions 40 --branches 40` pelos valores `(real-2)` de cada métrica (ex.: `--statements 77 --lines 77 --functions 80 --branches 73`). Mantenha como **piso**; a catraca da Fase 4 cuidará do "não-cair".
|
||||
|
||||
- [ ] **Step 3: Alinhar o script local e o display do comentário** para os mesmos números (procure `60` em `package.json` `test:coverage` e em `scripts/check/test-report-summary.mjs`).
|
||||
|
||||
- [ ] **Step 4: Verificar que o CI não quebra** — abrir um PR de teste (ou rodar `act`/push numa branch) e confirmar que o job `test-coverage` fica verde com o novo piso.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
```bash
|
||||
git add .github/workflows/ci.yml package.json scripts/check/test-report-summary.mjs
|
||||
git commit -m "fix(ci): reconcile coverage gate to real baseline (40->~78) across CI/local/report"
|
||||
```
|
||||
|
||||
### Task 0.2: Escalonar `npm audit` (critical=bloqueia / high=avisa)
|
||||
|
||||
**Files:** Modify: `package.json:112`
|
||||
|
||||
- [ ] **Step 1: Trocar o script `audit:deps`** de `npm audit --audit-level=moderate && npm run audit:electron` para:
|
||||
```json
|
||||
"audit:deps": "npm audit --audit-level=critical && (npm audit --audit-level=high || echo '::warning::high-severity advisories present (non-blocking)') && npm run audit:electron",
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rodar e observar o comportamento**
|
||||
```bash
|
||||
npm run audit:deps; echo "exit=$?"
|
||||
```
|
||||
Expected: `exit=0` se não houver critical; mensagem de warning se houver high.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
```bash
|
||||
git add package.json && git commit -m "chore(ci): tier npm audit (critical blocks, high warns)"
|
||||
```
|
||||
|
||||
### Task 0.3: Plugar os 3 scripts órfãos no CI
|
||||
|
||||
**Contexto:** `check:cli-i18n`, `check:openapi-coverage`, `check:openapi-security-tiers` existem, dão `exit 1`, mas não rodam em lugar nenhum (Hard Rules #15/#17).
|
||||
|
||||
**Files:** Modify: `.github/workflows/ci.yml` (job `docs-sync-strict` ou `lint`)
|
||||
|
||||
- [ ] **Step 1: Rodar os 3 localmente para confirmar verde no estado atual**
|
||||
```bash
|
||||
npm run check:cli-i18n && npm run check:openapi-coverage && npm run check:openapi-security-tiers; echo "exit=$?"
|
||||
```
|
||||
Expected: `exit=0` (se algum falhar, corrija a deriva antes de plugar).
|
||||
|
||||
- [ ] **Step 2: Adicionar os 3 como steps** no job `docs-sync-strict` do `ci.yml`, após `check:docs-all`.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
```bash
|
||||
git add .github/workflows/ci.yml && git commit -m "ci: wire orphaned gates (cli-i18n, openapi-coverage, openapi-security-tiers)"
|
||||
```
|
||||
|
||||
### Task 0.4: Reativar a parte barata do pre-commit do Husky
|
||||
|
||||
**Files:** Modify: `.husky/pre-commit`
|
||||
|
||||
- [ ] **Step 1: Descomentar SÓ as 3 linhas baratas e determinísticas:**
|
||||
```sh
|
||||
npx lint-staged
|
||||
node scripts/check/check-docs-sync.mjs
|
||||
npm run check:any-budget:t11
|
||||
```
|
||||
(Deixe i18n/openapi comentados por enquanto — eles são mais lentos; rodam no CI.)
|
||||
|
||||
- [ ] **Step 2: Testar o hook** com um commit trivial e medir o tempo:
|
||||
```bash
|
||||
time git commit --allow-empty -m "chore: test pre-commit hook"
|
||||
git reset --soft HEAD~1
|
||||
```
|
||||
Expected: hook roda lint-staged + 2 checks em poucos segundos.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
```bash
|
||||
git add .husky/pre-commit && git commit -m "chore(husky): re-enable cheap pre-commit gates (lint-staged, docs-sync, any-budget)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# FASE 1 — Motor de Catraca (o coração) ⭐
|
||||
|
||||
> Generaliza o `check-t11-any-budget.mjs` (catraca de `any` por arquivo) para um motor de catraca genérico, multi-métrica, que lê um baseline commitado e falha em qualquer regressão. Começa com 2 métricas (warnings de ESLint + cobertura) e é estendido nas fases seguintes.
|
||||
|
||||
### Task 1.1: Comparador genérico de catraca (TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/quality/check-quality-ratchet.mjs`
|
||||
- Test: `tests/unit/quality-ratchet.test.ts`
|
||||
|
||||
- [ ] **Step 1: Escrever o teste que falha**
|
||||
```ts
|
||||
// tests/unit/quality-ratchet.test.ts
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const SCRIPT = path.resolve("scripts/quality/check-quality-ratchet.mjs");
|
||||
|
||||
function run(baseline, metrics, extraArgs = []) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ratchet-"));
|
||||
const bPath = path.join(dir, "baseline.json");
|
||||
const mPath = path.join(dir, "metrics.json");
|
||||
fs.writeFileSync(bPath, JSON.stringify(baseline));
|
||||
fs.writeFileSync(mPath, JSON.stringify(metrics));
|
||||
try {
|
||||
const out = execFileSync("node", [SCRIPT, "--baseline", bPath, "--metrics", mPath, ...extraArgs], { encoding: "utf8" });
|
||||
return { code: 0, out, dir, bPath };
|
||||
} catch (e) {
|
||||
return { code: e.status, out: (e.stdout || "") + (e.stderr || ""), dir, bPath };
|
||||
}
|
||||
}
|
||||
|
||||
test("passes when metrics equal baseline", () => {
|
||||
const b = { metrics: { eslintWarnings: { value: 100, direction: "down" }, "coverage.lines": { value: 80, direction: "up" } } };
|
||||
assert.equal(run(b, { eslintWarnings: 100, "coverage.lines": 80 }).code, 0);
|
||||
});
|
||||
|
||||
test("fails when a 'down' metric regresses (more warnings)", () => {
|
||||
const b = { metrics: { eslintWarnings: { value: 100, direction: "down" } } };
|
||||
const r = run(b, { eslintWarnings: 101 });
|
||||
assert.equal(r.code, 1);
|
||||
assert.match(r.out, /eslintWarnings/);
|
||||
});
|
||||
|
||||
test("fails when an 'up' metric regresses (coverage drops)", () => {
|
||||
const b = { metrics: { "coverage.lines": { value: 80, direction: "up" } } };
|
||||
assert.equal(run(b, { "coverage.lines": 79 }).code, 1);
|
||||
});
|
||||
|
||||
test("passes on improvement; --update ratchets the baseline", () => {
|
||||
const b = { metrics: { eslintWarnings: { value: 100, direction: "down" } } };
|
||||
const r = run(b, { eslintWarnings: 90 }, ["--update"]);
|
||||
assert.equal(r.code, 0);
|
||||
const updated = JSON.parse(fs.readFileSync(r.bPath, "utf8"));
|
||||
assert.equal(updated.metrics.eslintWarnings.value, 90);
|
||||
});
|
||||
|
||||
test("fails (code 2) when a baseline metric is missing from collected metrics", () => {
|
||||
const b = { metrics: { eslintWarnings: { value: 100, direction: "down" } } };
|
||||
assert.equal(run(b, {}).code, 1);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rodar o teste e ver falhar**
|
||||
```bash
|
||||
node --import tsx --test tests/unit/quality-ratchet.test.ts
|
||||
```
|
||||
Expected: FAIL (script não existe ainda).
|
||||
|
||||
- [ ] **Step 3: Implementar o comparador**
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
// scripts/quality/check-quality-ratchet.mjs
|
||||
// Catraca genérica multi-métrica. Clona o espírito de check-t11-any-budget.mjs:
|
||||
// um baseline congelado por métrica; falha em qualquer regressão; só anda num sentido.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const cwd = process.cwd();
|
||||
function getArg(name, fallback) {
|
||||
const i = process.argv.indexOf(name);
|
||||
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
const BASELINE = path.resolve(getArg("--baseline", path.join(cwd, "quality-baseline.json")));
|
||||
const METRICS = path.resolve(getArg("--metrics", path.join(cwd, "quality-metrics.json")));
|
||||
const SUMMARY = getArg("--summary", null);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const EPS = 0.01;
|
||||
|
||||
function load(p) {
|
||||
if (!fs.existsSync(p)) { console.error(`[quality-ratchet] arquivo ausente: ${p}`); process.exit(2); }
|
||||
return JSON.parse(fs.readFileSync(p, "utf8"));
|
||||
}
|
||||
|
||||
const baseline = load(BASELINE);
|
||||
const metrics = load(METRICS);
|
||||
const failures = [];
|
||||
const improvements = [];
|
||||
const rows = [];
|
||||
|
||||
for (const [key, spec] of Object.entries(baseline.metrics)) {
|
||||
const current = metrics[key];
|
||||
const base = spec.value;
|
||||
const dir = spec.direction; // "down" = menor-é-melhor | "up" = maior-é-melhor
|
||||
if (current === undefined) { failures.push(`métrica "${key}" ausente em ${path.basename(METRICS)}`); rows.push([key, base, "—", "MISSING"]); continue; }
|
||||
let status = "ok";
|
||||
if (dir === "down") {
|
||||
if (current > base + EPS) { failures.push(`${key}: ${current} > baseline ${base} (não pode aumentar)`); status = "REGRESSÃO"; }
|
||||
else if (current < base - EPS) { improvements.push([key, current]); status = "↑ melhorou"; }
|
||||
} else {
|
||||
if (current < base - EPS) { failures.push(`${key}: ${current} < baseline ${base} (não pode cair)`); status = "REGRESSÃO"; }
|
||||
else if (current > base + EPS) { improvements.push([key, current]); status = "↑ melhorou"; }
|
||||
}
|
||||
rows.push([key, base, current, status]);
|
||||
}
|
||||
|
||||
if (SUMMARY) {
|
||||
const md = ["# Quality Ratchet", "", "| Métrica | Baseline | Atual | Status |", "|---|---|---|---|",
|
||||
...rows.map(([k, b, c, s]) => `| ${k} | ${b} | ${c} | ${s} |`), "",
|
||||
failures.length ? `**${failures.length} regressão(ões) — gate BLOQUEADO.**` : "**Sem regressões — gate OK.**"].join("\n");
|
||||
fs.mkdirSync(path.dirname(SUMMARY), { recursive: true });
|
||||
fs.writeFileSync(SUMMARY, md + "\n");
|
||||
}
|
||||
|
||||
if (UPDATE && failures.length === 0 && improvements.length) {
|
||||
for (const [key, val] of improvements) baseline.metrics[key].value = val;
|
||||
fs.writeFileSync(BASELINE, JSON.stringify(baseline, null, 2) + "\n");
|
||||
console.log(`[quality-ratchet] baseline ratcheado: ${improvements.length} métrica(s) melhoraram`);
|
||||
}
|
||||
|
||||
if (failures.length) { console.error("[quality-ratchet] FALHOU:\n" + failures.map((f) => " ✗ " + f).join("\n")); process.exit(1); }
|
||||
console.log(`[quality-ratchet] OK (${rows.length} métricas, ${improvements.length} melhoraram)`);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Rodar o teste e ver passar**
|
||||
```bash
|
||||
node --import tsx --test tests/unit/quality-ratchet.test.ts
|
||||
```
|
||||
Expected: PASS (5/5).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
```bash
|
||||
git add scripts/quality/check-quality-ratchet.mjs tests/unit/quality-ratchet.test.ts
|
||||
git commit -m "feat(quality): generic ratchet comparator (multi-metric, regression-only)"
|
||||
```
|
||||
|
||||
### Task 1.2: Coletor de métricas (ESLint warnings + cobertura)
|
||||
|
||||
**Files:** Create: `scripts/quality/collect-metrics.mjs`
|
||||
|
||||
- [ ] **Step 1: Implementar o coletor**
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
// scripts/quality/collect-metrics.mjs — emite quality-metrics.json
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const out = {};
|
||||
|
||||
// 1) ESLint: contagem de warnings (errors devem ser 0; o lint já gata isso)
|
||||
function eslintCounts() {
|
||||
let stdout;
|
||||
try {
|
||||
stdout = execFileSync("npx", ["eslint", ".", "--format", "json"], { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
||||
} catch (e) { stdout = e.stdout?.toString() || "[]"; } // eslint sai !=0 quando há errors
|
||||
const results = JSON.parse(stdout);
|
||||
out.eslintWarnings = results.reduce((n, r) => n + (r.warningCount || 0), 0);
|
||||
out.eslintErrors = results.reduce((n, r) => n + (r.errorCount || 0), 0);
|
||||
}
|
||||
|
||||
// 2) Cobertura: lê coverage/coverage-summary.json se existir
|
||||
function coverage() {
|
||||
const p = path.join(cwd, "coverage", "coverage-summary.json");
|
||||
if (!fs.existsSync(p)) return;
|
||||
const t = JSON.parse(fs.readFileSync(p, "utf8")).total;
|
||||
out["coverage.statements"] = t.statements.pct;
|
||||
out["coverage.lines"] = t.lines.pct;
|
||||
out["coverage.functions"] = t.functions.pct;
|
||||
out["coverage.branches"] = t.branches.pct;
|
||||
}
|
||||
|
||||
eslintCounts();
|
||||
coverage();
|
||||
fs.writeFileSync(path.join(cwd, "quality-metrics.json"), JSON.stringify(out, null, 2) + "\n");
|
||||
console.log("[collect-metrics]", JSON.stringify(out));
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rodar e inspecionar a saída**
|
||||
```bash
|
||||
node scripts/quality/collect-metrics.mjs && cat quality-metrics.json
|
||||
```
|
||||
Expected: JSON com `eslintWarnings`, `eslintErrors` e (se houver coverage) os 4 `coverage.*`. **Anote `eslintWarnings`.**
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
```bash
|
||||
git add scripts/quality/collect-metrics.mjs && echo "quality-metrics.json" >> .gitignore
|
||||
git add .gitignore && git commit -m "feat(quality): metrics collector (eslint warnings + coverage)"
|
||||
```
|
||||
|
||||
### Task 1.3: Congelar o baseline inicial
|
||||
|
||||
**Files:** Create: `quality-baseline.json` (**commitado**)
|
||||
|
||||
- [ ] **Step 1: Gerar o baseline a partir das métricas reais** (use os números anotados):
|
||||
```json
|
||||
{
|
||||
"_comment": "Catraca: 'down' nao pode aumentar, 'up' nao pode cair. Atualize via 'npm run quality:ratchet -- --update' (so em melhora).",
|
||||
"metrics": {
|
||||
"eslintWarnings": { "value": <N_REAL>, "direction": "down" },
|
||||
"coverage.statements": { "value": <S>, "direction": "up" },
|
||||
"coverage.lines": { "value": <L>, "direction": "up" },
|
||||
"coverage.functions": { "value": <F>, "direction": "up" },
|
||||
"coverage.branches": { "value": <B>, "direction": "up" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Validar a catraca contra si mesma**
|
||||
```bash
|
||||
node scripts/quality/collect-metrics.mjs
|
||||
node scripts/quality/check-quality-ratchet.mjs; echo "exit=$?"
|
||||
```
|
||||
Expected: `[quality-ratchet] OK` e `exit=0`.
|
||||
|
||||
- [ ] **Step 3: Provar que pega regressão** (teste manual): edite `quality-metrics.json` somando 1 a `eslintWarnings`, rode o comparador, confirme `exit=1`, depois descarte a edição.
|
||||
|
||||
- [ ] **Step 4: Adicionar scripts npm**
|
||||
```json
|
||||
"quality:collect": "node scripts/quality/collect-metrics.mjs",
|
||||
"quality:ratchet": "node scripts/quality/check-quality-ratchet.mjs",
|
||||
"quality:gate": "npm run quality:collect && npm run quality:ratchet"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
```bash
|
||||
git add quality-baseline.json package.json
|
||||
git commit -m "feat(quality): freeze initial quality baseline (eslint warnings + coverage)"
|
||||
```
|
||||
|
||||
### Task 1.4: Wire no CI (job + artefato + comentário no PR)
|
||||
|
||||
**Files:** Modify: `.github/workflows/ci.yml`
|
||||
|
||||
- [ ] **Step 1: Adicionar job `quality-gate`** (depois de `test-coverage`, para reusar `coverage/coverage-summary.json`):
|
||||
```yaml
|
||||
quality-gate:
|
||||
name: Quality Ratchet
|
||||
runs-on: ubuntu-latest
|
||||
needs: test-coverage
|
||||
if: ${{ always() && needs.test-coverage.result == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with: { node-version: '24', cache: 'npm' }
|
||||
- run: npm ci
|
||||
- uses: actions/download-artifact@v4
|
||||
with: { name: coverage-report, path: coverage/ }
|
||||
- run: npm run quality:collect
|
||||
- run: node scripts/quality/check-quality-ratchet.mjs --summary .artifacts/quality-ratchet.md
|
||||
- if: always()
|
||||
run: cat .artifacts/quality-ratchet.md >> "$GITHUB_STEP_SUMMARY"
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with: { name: quality-ratchet, path: .artifacts/quality-ratchet.md }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Adicionar comentário no PR** clonando o job `coverage-pr-comment` (marcador `<!-- omniroute-quality-ratchet -->`, lê `.artifacts/quality-ratchet.md`). Reuse o mesmo `github-script` de upsert de comentário.
|
||||
|
||||
- [ ] **Step 3: Validar num PR de teste** — confirmar que o job aparece, o step summary mostra a tabela, e o comentário é postado.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
```bash
|
||||
git add .github/workflows/ci.yml
|
||||
git commit -m "ci(quality): add quality-ratchet job with PR comment + artifact"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# FASE 2 — Gates Determinísticos Anti-Alucinação
|
||||
|
||||
> Cada gate ataca um ímã de alucinação específico (ver §2.4 do relatório). Todos no padrão `check-*.mjs`. **`check-fetch-targets` vem com código completo** (parsing determinístico de arquivos, sem risco de inventar nomes de export). **`check-provider-consistency` e `check-openapi-routes` começam com um Step 0 de verificação dos nomes reais** — propositalmente, porque fabricar a forma do import/spec seria o exato anti-padrão que este plano combate.
|
||||
|
||||
### Task 2.1: `check-fetch-targets.mjs` — toda rota chamada pelo dashboard existe (TDD)
|
||||
|
||||
**Ataca:** ímã nº2 (300 paths `fetch("/api/...")` sem ligação com as rotas).
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/check/check-fetch-targets.mjs`
|
||||
- Test: `tests/unit/check-fetch-targets.test.ts`
|
||||
|
||||
- [ ] **Step 1: Escrever o teste que falha**
|
||||
```ts
|
||||
// tests/unit/check-fetch-targets.test.ts
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { resolveApiPathToRoute } from "../../scripts/check/check-fetch-targets.mjs";
|
||||
|
||||
test("matches a static route file", () => {
|
||||
const files = new Set(["src/app/api/usage/route.ts"]);
|
||||
assert.equal(resolveApiPathToRoute("/api/usage", files), true);
|
||||
});
|
||||
|
||||
test("matches a dynamic [param] segment", () => {
|
||||
const files = new Set(["src/app/api/providers/[id]/models/route.ts"]);
|
||||
assert.equal(resolveApiPathToRoute("/api/providers/abc-123/models", files), true);
|
||||
});
|
||||
|
||||
test("rejects a hallucinated route", () => {
|
||||
const files = new Set(["src/app/api/usage/route.ts"]);
|
||||
assert.equal(resolveApiPathToRoute("/api/providers/refresh", files), false);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rodar e ver falhar**
|
||||
```bash
|
||||
node --import tsx --test tests/unit/check-fetch-targets.test.ts
|
||||
```
|
||||
Expected: FAIL (módulo não existe).
|
||||
|
||||
- [ ] **Step 3: Implementar o gate**
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
// scripts/check/check-fetch-targets.mjs
|
||||
// Todo fetch("/api/...") em src/app/(dashboard) deve resolver para um route.ts real.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const DASH = path.join(cwd, "src/app/(dashboard)");
|
||||
const API = path.join(cwd, "src/app/api");
|
||||
|
||||
// allowlist de paths dinâmicos/externos que o checker não consegue resolver estaticamente
|
||||
const IGNORE = [/^\/api\/v1\//, /\$\{/, /` \+/]; // /v1 é a superfície OpenAI-compat; templates
|
||||
|
||||
function walk(dir, acc = []) {
|
||||
if (!fs.existsSync(dir)) return acc;
|
||||
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, e.name);
|
||||
if (e.isDirectory()) walk(p, acc);
|
||||
else if (/\.(ts|tsx)$/.test(e.name)) acc.push(p);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
function collectRouteFiles() {
|
||||
return new Set(walk(API).filter((p) => /route\.tsx?$/.test(p)).map((p) => path.relative(cwd, p).replace(/\\/g, "/")));
|
||||
}
|
||||
|
||||
export function resolveApiPathToRoute(apiPath, routeFiles) {
|
||||
// apiPath ex.: /api/providers/abc/models → src/app/api/providers/[id]/models/route.ts
|
||||
const segs = apiPath.replace(/^\//, "").replace(/[?#].*$/, "").split("/"); // ["api","providers","abc","models"]
|
||||
for (const rf of routeFiles) {
|
||||
const rsegs = rf.replace(/^src\/app\//, "").replace(/\/route\.tsx?$/, "").split("/"); // ["api","providers","[id]","models"]
|
||||
if (rsegs.length !== segs.length) continue;
|
||||
const ok = rsegs.every((rs, i) => rs === segs[i] || /^\[.*\]$/.test(rs));
|
||||
if (ok) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractFetchPaths(file) {
|
||||
const src = fs.readFileSync(file, "utf8");
|
||||
const re = /(?:fetch|fetchJson|apiFetch)\(\s*["'`](\/api\/[^"'`?]+)/g;
|
||||
const out = [];
|
||||
let m;
|
||||
while ((m = re.exec(src))) out.push(m[1]);
|
||||
return out;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const routeFiles = collectRouteFiles();
|
||||
const misses = [];
|
||||
for (const f of walk(DASH)) {
|
||||
for (const apiPath of extractFetchPaths(f)) {
|
||||
if (IGNORE.some((rx) => rx.test(apiPath))) continue;
|
||||
if (!resolveApiPathToRoute(apiPath, routeFiles)) misses.push(`${path.relative(cwd, f)} → ${apiPath}`);
|
||||
}
|
||||
}
|
||||
if (misses.length) {
|
||||
console.error(`[check-fetch-targets] ${misses.length} fetch(es) para rota inexistente:\n` + misses.map((m) => " ✗ " + m).join("\n"));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`[check-fetch-targets] OK (${routeFiles.size} rotas conhecidas)`);
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) main();
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Rodar o teste unitário e ver passar**
|
||||
```bash
|
||||
node --import tsx --test tests/unit/check-fetch-targets.test.ts
|
||||
```
|
||||
Expected: PASS (3/3).
|
||||
|
||||
- [ ] **Step 5: Rodar o gate no repo real** (modo descoberta — pode encontrar misses legítimos)
|
||||
```bash
|
||||
node scripts/check/check-fetch-targets.mjs; echo "exit=$?"
|
||||
```
|
||||
Se encontrar misses reais: triagem — ou são rotas faltantes (bug), ou paths dinâmicos a adicionar ao `IGNORE`. **Não** mascare; documente cada exceção no `IGNORE` com comentário.
|
||||
|
||||
- [ ] **Step 6: Adicionar script + commit**
|
||||
```json
|
||||
"check:fetch-targets": "node scripts/check/check-fetch-targets.mjs"
|
||||
```
|
||||
```bash
|
||||
git add scripts/check/check-fetch-targets.mjs tests/unit/check-fetch-targets.test.ts package.json
|
||||
git commit -m "feat(check): gate that every dashboard fetch() targets a real API route"
|
||||
```
|
||||
|
||||
### Task 2.2: `check-provider-consistency.ts` — ids batem entre os 3 arquivos
|
||||
|
||||
**Ataca:** ímã nº1 (split de provider; 229 vs 155 vs N já divergem).
|
||||
|
||||
**Files:** Create: `scripts/check/check-provider-consistency.ts` (rodado via `tsx`); Test: `tests/unit/check-provider-consistency.test.ts`
|
||||
|
||||
- [ ] **Step 0 (VERIFICAÇÃO — obrigatório antes de codar):** descobrir os nomes reais de export, para **não inventar**:
|
||||
```bash
|
||||
grep -nE "export (const|function) " src/shared/constants/providers.ts | grep -iE "provider|section" | head
|
||||
grep -nE "getOrCreateAiProviders|_PROVIDER_SECTIONS|AI_PROVIDERS" src/shared/constants/providers.ts | head
|
||||
grep -nE "baseUrl|^\s*['\"][a-z0-9-]+['\"]\s*:" open-sse/config/providerRegistry.ts | head
|
||||
grep -nE "case ['\"]|=== ['\"]|SPECIALTY_VALIDATORS" src/lib/providers/validation.ts | head
|
||||
```
|
||||
Anote: como enumerar os ids canônicos em runtime, a forma das chaves do `providerRegistry.ts`, e onde `validation.ts` lista os providers cobertos.
|
||||
|
||||
- [ ] **Step 1: Escrever o teste** com fixtures sintéticas (3 conjuntos de ids), afirmando que o diff detecta um id presente em um e ausente em outro. (Implemente a lógica como função pura `diffProviderSets(canonical, registry, validators)` que retorna `{missingInRegistry, missingInValidators, orphanInRegistry}`.)
|
||||
|
||||
- [ ] **Step 2: Rodar e ver falhar.**
|
||||
|
||||
- [ ] **Step 3: Implementar** importando os ids reais (descobertos no Step 0) e cruzando-os. Reusar o padrão de `scripts/check/check-docs-counts-sync.mjs` (que **já conta** executors/oauth/strategies vs docs — é o template direto). Para ids que legitimamente vivem só num lugar, manter um `KNOWN_EXCEPTIONS` allowlist com comentário por entrada.
|
||||
|
||||
- [ ] **Step 4: Rodar no repo real**, triar as divergências reais (as contagens já divergem — algumas serão bugs de registro pela metade, outras exceções legítimas).
|
||||
|
||||
- [ ] **Step 5: Commit** (`feat(check): provider-id consistency gate across providers.ts/registry/validation`).
|
||||
|
||||
**Critério de aceitação:** o gate falha quando um id existe em `providers.ts` mas não no `providerRegistry.ts` (ou vice-versa), e quando um model no registry referencia um provider id desconhecido.
|
||||
|
||||
### Task 2.3: `check-openapi-routes.mjs` — spec ↔ rotas (bidirecional)
|
||||
|
||||
**Ataca:** docs-hallucination (endpoint inventado no `openapi.yaml`).
|
||||
|
||||
**Files:** Create: `scripts/check/check-openapi-routes.mjs`; Test correspondente.
|
||||
|
||||
- [ ] **Step 0 (VERIFICAÇÃO):** confirmar o caminho e a forma do spec:
|
||||
```bash
|
||||
ls docs/reference/openapi.yaml && grep -nE "^\s{2,4}/[a-z]" docs/reference/openapi.yaml | head
|
||||
```
|
||||
Confirmar como `check-openapi-coverage.mjs` já parseia o YAML (reusar o parser dele).
|
||||
|
||||
- [ ] **Step 1–5 (TDD):** teste com spec sintética → implementar: toda `path` do spec resolve para um `route.ts` (reusando `resolveApiPathToRoute` da Task 2.1) e toda rota não-interna aparece no spec (com allowlist para rotas LOCAL_ONLY/internas). Commit.
|
||||
|
||||
### Task 2.4: `check-deps.mjs` — anti-slopsquatting
|
||||
|
||||
**Ataca:** pacotes alucinados (CSA: 19,7% das amostras IA).
|
||||
|
||||
**Files:** Create: `scripts/check/check-deps.mjs`; Test.
|
||||
|
||||
- [ ] **Step 0 (VERIFICAÇÃO):** decidir política — allowlist = todas as deps atuais do `package.json`/lockfile como baseline; novas deps exigem (a) existir no registry, (b) idade ≥ 72h, (c) entrada explícita.
|
||||
- [ ] **Step 1–5 (TDD):** teste com `package.json` sintético adicionando uma dep nova → gate falha se a dep não está no baseline E (não existe no registry OU foi publicada há <72h). Usar `npm view <pkg> time.created` para idade. Reusar o padrão diff-base...HEAD de `check-pr-test-policy.mjs`. Commit.
|
||||
|
||||
### Task 2.5 (opcional): lints Rule #11/#12 via `no-restricted-syntax`
|
||||
|
||||
- [ ] Estender o `eslint.config.mjs` (bloco `no-restricted-syntax` já usado p/ a regra de busca turca) para sinalizar `NextResponse.json({ error: "<string>" })` cru (exigir `buildErrorBody()`) e literais de `clientIdDefault`/`clientSecretDefault` no `providerRegistry.ts` (exigir `resolvePublicCred()`). Ratchet via contagem (adoção 11% → sobe). TDD com fixtures de ESLint.
|
||||
|
||||
**Plugar tudo da Fase 2 no CI:** adicionar `check:fetch-targets`, `check:provider-consistency`, `check:openapi-routes`, `check:deps` ao job `lint` (ou `docs-sync-strict`) do `ci.yml`, e ao pre-commit barato quando rápidos o suficiente.
|
||||
|
||||
---
|
||||
|
||||
# FASE 3 — Catraca de Duplicação + Tamanho (mata-slop) — *expandir em sub-plano*
|
||||
|
||||
**Justificativa:** GitClear mostra duplicação 4–8× na era IA; é a assinatura nº1 de slop. Nenhum gate hoje (Sonar CPD excluído).
|
||||
|
||||
**Specs (criar sub-plano `writing-plans` a partir daqui):**
|
||||
|
||||
1. **Adicionar `jscpd` v5** como devDependency. Rodar `jscpd --reporters json src open-sse` → `quality-metrics.json` ganha `duplication.pct`. **[verificar no install]** o schema JSON do v5 antes de parsear (ver ressalva no relatório §4.2).
|
||||
2. **Estender `collect-metrics.mjs`** com um coletor de duplicação (lê o JSON do jscpd) e um coletor de **tamanho de arquivo** (conta LOC de cada arquivo em `src`+`open-sse`, emite `fileSize.<path>` para os arquivos já acima de um teto, ex. >700 LOC — congelando os 64 atuais).
|
||||
3. **Adicionar regras ESLint** em `eslint.config.mjs` como `warn`: `max-lines` (700), `max-lines-per-function` (80), `complexity` (15), `sonarjs/cognitive-complexity` (15). Coletor adiciona `eslintWarnings` por categoria (a contagem já está na catraca da Fase 1; opcionalmente segregar `complexityWarnings`).
|
||||
4. **Estender `quality-baseline.json`** com `duplication.pct` (`down`) e os `fileSize.*` dos arquivos grandes (`down` — só podem encolher; arquivos novos têm teto absoluto).
|
||||
5. **Teto absoluto para arquivos novos:** o gate falha se um arquivo **não** presente no baseline nasce acima do teto (ex. 700 LOC) — impede o próximo god-component.
|
||||
|
||||
**Critérios de aceitação:** PR que aumenta a duplicação % falha; PR que cresce qualquer um dos 64 arquivos grandes falha; PR que cria arquivo novo >700 LOC falha; refator que encolhe um arquivo grande ratcheta o baseline para baixo (via `--update`).
|
||||
|
||||
---
|
||||
|
||||
# FASE 4 — Catraca de Cobertura + Anti Test-Masking — *expandir em sub-plano*
|
||||
|
||||
**Specs:**
|
||||
|
||||
1. **`check-coverage-ratchet`** já é coberto pelo motor da Fase 1 (as 4 métricas `coverage.*` com `direction: "up"`). Confirmar que o job `quality-gate` roda **após** o merge dos 8 shards de cobertura no CI (não localmente, onde a cobertura é parcial). Adicionar **epsilon** maior (ex. 0.5) para `coverage.branches` por causa do não-determinismo do v8.
|
||||
2. **Pisos por módulo crítico:** estender `collect-metrics.mjs` para emitir `coverage.<modulo>.lines` para uma lista curta de módulos de alto risco (lendo o `coverage-summary.json` por arquivo): `open-sse/handlers/chatCore.ts`, `open-sse/services/combo.ts`, `open-sse/services/accountFallback.ts`, `src/sse/services/auth.ts`, `src/server/authz/routeGuard.ts`, `open-sse/utils/error.ts`, `open-sse/utils/publicCreds.ts`, `src/shared/utils/circuitBreaker.ts`. Cada um vira métrica `up` no baseline (implementa o "risco, não % bruto" do vídeo).
|
||||
3. **`check-test-masking.mjs`** (anti enfraquecimento de asserts): clonar `check-pr-test-policy.mjs` (diff `base...HEAD`); para cada `*.test.ts`/`*.spec.ts` alterado, contar `assert*(`/`expect(` em base vs HEAD; **sinalizar remoção líquida** de asserts para revisão humana. Banir novos `assert.ok(true)`. Heurístico mas alto-sinal — ataca diretamente o risco organizacional nº1 ("subagente deletou asserts para ficar verde").
|
||||
|
||||
**Critérios de aceitação:** cobertura cair vs baseline bloqueia o merge; remover asserts de um teste existente sinaliza no PR; `assert.ok(true)` novo bloqueia.
|
||||
|
||||
---
|
||||
|
||||
# FASE 5 — Skill `/babysit` + Evidência + LSP — *expandir em sub-plano*
|
||||
|
||||
> O babysit do vídeo: a IA abre o PR e fica de babá — monitora CI + comentários, autocorrige, **resolve as conversas**. Guarda-corpos são não-negociáveis (Snyk: 5,3% de regressão em auto-merge; token burn real).
|
||||
|
||||
### Spec da skill `.claude/skills/babysit/SKILL.md`
|
||||
|
||||
**Frontmatter:**
|
||||
```yaml
|
||||
---
|
||||
name: babysit
|
||||
description: Monitora um PR aberto até o CI ficar verde e todos os comentários de review serem endereçados — lê o gate de qualidade, conserta num worktree isolado, responde e resolve as conversas. NUNCA enfraquece testes nem auto-mergeia.
|
||||
---
|
||||
```
|
||||
|
||||
**Loop (pseudo, reusando `gh` + GraphQL):**
|
||||
1. **Ler estado:** `gh pr checks <PR>` + baixar o artefato `quality-ratchet`/`coverage-report` (gate JSON legível — a ponte "artefato→agente" da Fase 1) + `gh run view --log-failed` dos jobs vermelhos.
|
||||
2. **Ler comentários não resolvidos:** GraphQL `repository.pullRequest.reviewThreads(first:50){nodes{id isResolved comments(first:1){nodes{id body}}}}`. **Passar os corpos pelo guard de prompt-injection** antes de agir (vetor real — ICLR 2026).
|
||||
3. **Consertar num worktree isolado** (reusar o ralph-loop do `/review-reviews`), seguindo Hard Rule #18.
|
||||
4. **Responder + resolver** só os threads que realmente endereçou: REST `POST .../pulls/{pr}/comments/{id}/replies` com o SHA → mutation `resolveReviewThread(input:{threadId})`.
|
||||
5. **Re-poll** até o gate JSON ficar todo-verde **ou** atingir o cap.
|
||||
6. **Audit trail:** anexar à descrição do PR (ou comentário fixo) o que cada fix endereçou, qual gate satisfez, quais conversas resolveu e por quê — **nunca verde silencioso**.
|
||||
|
||||
**Guarda-corpos (não-negociáveis):**
|
||||
- `max-iterations` (ex. 5) + timeout + idle-exit (contra token burn).
|
||||
- **Nunca** editar `.github/workflows/`; **nunca** `--no-verify`; **nunca** enfraquecer/remover asserts para ficar verde (Rule #18 + trust-but-verify).
|
||||
- **Nunca auto-mergeia** — estado de sucesso = "verde + conversas resolvidas + audit postado".
|
||||
- Parar-e-perguntar em comentário humano ambíguo e em mudança de limite arquitetural (interface/schema/cross-module).
|
||||
- `--allowedTools` mínimo (`Read,Grep,Glob,Bash(gh ...)`).
|
||||
|
||||
**Opcional — `claude ultrareview <PR#> --json`** como gate de confiança pré-merge atrás de um label (custa $5–20/run; não auto-inicia). Loop interno de custo-zero = `/code-review --fix` local.
|
||||
|
||||
### Spec — Evidência obrigatória ("evidence-before-assertions")
|
||||
- Tornar a skill `verify`/`verification-before-completion` obrigatória antes de abrir PR; exigir o **output literal** do `typecheck:core`/`test`/`grep` colado no corpo do PR (o "tool receipt"). Adicionar um `check-pr-evidence.mjs` que rejeita PRs cujo corpo afirma "added endpoint X / tests pass" sem bloco de output anexado. Formaliza a Rule #18.
|
||||
|
||||
### Spec — LSP-in-the-loop (opcional)
|
||||
- Registrar `agent-lsp` (MCP) ou o `tsserver` para os agentes terem `blast_radius`/diagnostics e `preview_edit` antes de escrever — vira "símbolo inventado" de catch-de-review para impossibilidade-no-edit. Pareia com `typecheck:core` como gate pré-PR (compile-before-claim).
|
||||
|
||||
**Critérios de aceitação:** a skill `/babysit <PR#>` leva um PR de vermelho a verde sem auto-merge, resolve só as conversas que endereçou, respeita o cap de iterações, e deixa rastro auditável; nenhum assert é enfraquecido.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review (checklist do autor)
|
||||
|
||||
- **Cobertura do spec:** Fases 0–2 mapeiam 1:1 com as recomendações do relatório §5; Fases 3–5 cobrem duplicação/tamanho, cobertura/masking e babysit/evidência. ✓
|
||||
- **Sem placeholders nos passos bite-sized:** Fases 0/1/2.1 têm comandos e código reais. As Fases 2.2–2.4 usam um Step-0 de verificação **deliberado** (não placeholder) para não fabricar nomes de export — coerente com o objetivo anti-alucinação. ✓
|
||||
- **Consistência de tipos:** `resolveApiPathToRoute` (Task 2.1) é reusada na Task 2.3; o comparador usa o mesmo formato `{value, direction}` em todas as fases; `quality-metrics.json` é a interface única coletor↔comparador. ✓
|
||||
- **Ordem de dependência:** Fase 1 (motor) precede 3/4 (que só adicionam métricas ao baseline); Fase 0 destrava o resto. ✓
|
||||
|
||||
## Handoff de Execução
|
||||
|
||||
**Plano salvo em `PLANO-QUALITY-GATES.md`.** Duas opções:
|
||||
|
||||
1. **Subagent-Driven (recomendado)** — um subagente fresco por task, review entre tasks. SUB-SKILL: `superpowers:subagent-driven-development`.
|
||||
2. **Inline** — executar nesta sessão com checkpoints. SUB-SKILL: `superpowers:executing-plans`.
|
||||
|
||||
**Recomendação:** começar pela **Fase 0** (quick wins, baixo risco) e **Fase 1** (motor de catraca) numa branch `feat/quality-ratchet`, validar num PR de teste, e só então abrir as fases anti-alucinação. Fases 3–5 viram sub-planos próprios.
|
||||
172
README.md
172
README.md
@@ -15,30 +15,12 @@
|
||||
|
||||
<br/>
|
||||
|
||||
**~1.9B+ documented free tokens/month** — up to **~2.5B in your first month** with signup credits — aggregated across the free tiers, and the compression above stretches every one further. ([how we count →](docs/reference/FREE_TIERS.md#tldr--how-much-free-inference-does-omniroute-actually-aggregate))
|
||||
|
||||
<br/>
|
||||
|
||||
[](#-177-ai-providers--50-free)
|
||||
[](#-177-ai-providers--50-free)
|
||||
[](docs/reference/FREE_TIERS.md)
|
||||
[](#%EF%B8%8F-save-1595-tokens--automatically)
|
||||
[](#-combos--the-flagship)
|
||||
[](#-quick-start)
|
||||
|
||||
<br/>
|
||||
|
||||
### 💬 Join the community
|
||||
|
||||
[](https://discord.gg/hmexnhgE)
|
||||
[](https://t.me/omnirouteOficial)
|
||||
[](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t)
|
||||
[](https://chat.whatsapp.com/CeGCxdFzqBe5Uki288wOvf)
|
||||
|
||||
**Questions, provider tips, roadmap & support → [Discord](https://discord.gg/hmexnhgE) · [Telegram](https://t.me/omnirouteOficial) · WhatsApp [🌍 Global](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) / [🇧🇷 Brasil](https://chat.whatsapp.com/CeGCxdFzqBe5Uki288wOvf)**
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://trendshift.io/repositories/23589" target="_blank"><img src="https://trendshift.io/api/badge/repositories/23589" alt="diegosouzapw%2FOmniRoute | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
[](https://www.npmjs.com/package/omniroute)
|
||||
@@ -59,7 +41,7 @@
|
||||
|
||||
<br/>
|
||||
|
||||
[**🚀 Quick Start**](#-quick-start) • [**🎯 Combos**](#-combos--the-flagship) • [**🌐 Providers**](#-177-ai-providers--50-free) • [**🔌 CLI & MCP**](#-full-cli--a2a--mcp) • [**🗜️ Compression**](#%EF%B8%8F-save-1595-tokens--automatically) • [**🌍 Website**](https://omniroute.online)
|
||||
[**🚀 Quick Start**](#-quick-start) • [**🎯 Combos**](#-combos--the-flagship) • [**🌐 Providers**](#-177-ai-providers--50-free) • [**🔌 CLI & MCP**](#-full-cli--a2a--mcp) • [**🗜️ Compression**](#%EF%B8%8F-save-1595-tokens--automatically) • [**🌍 Website**](https://omniroute.online) • [**💬 WhatsApp 🌍**](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) • [**💬 WhatsApp 🇧🇷**](https://chat.whatsapp.com/CeGCxdFzqBe5Uki288wOvf)
|
||||
|
||||
[💥 The Promise](#-the-promise) • [🤔 Why](#-why-omniroute) • [🏆 What Sets Apart](#-what-sets-omniroute-apart) • [🤖 Compatible CLIs](#-compatible-clis--coding-agents) • [🖥️ Where It Runs](#%EF%B8%8F-where-omniroute-runs--anywhere) • [🔒 Private](#-private--local-first) • [🎬 In Action](#-omniroute-in-action) • [📚 Explore More](#-explore-more) • [📧 Support](#-support--community)
|
||||
|
||||
@@ -114,24 +96,6 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
# 💰 ~1.9B Free Tokens / Month
|
||||
|
||||
</div>
|
||||
|
||||
> Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute aggregates the **documented** free tiers of **50+ provider pools / 530 models** into one honest number and shows it live on the dashboard (`/dashboard/free-tiers`).
|
||||
|
||||
- **~1.9B free tokens / month** (steady) — and **up to ~2.5B in your first month** with signup credits.
|
||||
- **Pool-deduped, honest** — we count each shared free pool **once**, so the headline isn't inflated by rate-limit ceilings the way multi-billion competitor claims are. (The naïve per-model sum would read ~8B; we don't publish that.)
|
||||
- **Per-model breakdown**, **live used / remaining** for the current month, and a transparent **terms flag** per provider.
|
||||
|
||||

|
||||
|
||||
> Preview mockup — a real screenshot lands once the `/dashboard/free-tiers` page is validated. Full methodology (pool dedupe, credit tiers, provider terms): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**.
|
||||
|
||||
<br/>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# 💥 The Promise
|
||||
|
||||
</div>
|
||||
@@ -311,7 +275,7 @@ Result: 4 layers of fallback = zero downtime
|
||||
<b>+ also works with</b> · Cline · Antigravity · Windsurf · AMP · Hermes · Qwen CLI · Roo · Continue · <b>any OpenAI-compatible tool</b>
|
||||
</div>
|
||||
|
||||
<sub>📖 Per-tool setup for all 16+ tools → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
|
||||
<sub>📖 Per-tool setup for all 16+ tools → [`docs/CLI-TOOLS.md`](docs/CLI-TOOLS.md) · 🧩 OpenCode plugin → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider)</sub>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -368,7 +332,7 @@ Result: 4 layers of fallback = zero downtime
|
||||
| 🧩 **OpenCode plugin** | `@omniroute/opencode-provider` | Native OpenCode integration |
|
||||
| 🛠️ **From source** | `npm install && npm run dev` | Hack on it, contribute |
|
||||
|
||||
<sub>📖 [Docker Guide](docs/guides/DOCKER_GUIDE.md) · [Desktop](electron/README.md) · [Termux](docs/guides/TERMUX_GUIDE.md) · [PWA](docs/guides/PWA_GUIDE.md) · [OpenCode](docs/frameworks/OPENCODE.md)</sub>
|
||||
<sub>📖 [Docker Guide](docs/DOCKER_GUIDE.md) · [Desktop](electron/README.md) · [Termux](docs/TERMUX_GUIDE.md) · [PWA](docs/PWA_GUIDE.md) · [OpenCode](docs/frameworks/OPENCODE.md)</sub>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -439,7 +403,7 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp
|
||||
|
||||
</div>
|
||||
|
||||
> **Why use many token when few token do trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It stacks ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 51K+), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR).
|
||||
> **Why use many token when few token do trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It stacks ideas from [RTK](https://github.com/rtk-ai/rtk) and [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 51K+).
|
||||
|
||||
| Mode | Savings | Best for |
|
||||
| ------------------------------ | ---------- | --------------------------- |
|
||||
@@ -458,14 +422,6 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp
|
||||
>
|
||||
> **Same answer. 72% fewer tokens. Zero accuracy loss.** ✅
|
||||
|
||||
**PT-BR example — [Troglodita](https://github.com/leninejunior/troglodita) mode:**
|
||||
|
||||
> **Antes (42 tokens):** _"O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."_
|
||||
>
|
||||
> **Depois (12 tokens):** _"Re-render: ref nova cada ciclo (objeto inline recriado). Usar `useMemo`."_
|
||||
>
|
||||
> **Mesma resposta. ~70% menos tokens. Precisão técnica intacta.** ✅
|
||||
|
||||
<br/>
|
||||
|
||||
### 📖 How it works — pipeline, architecture & savings math</b></summary>
|
||||
@@ -484,7 +440,7 @@ range = 78.4 – 94.6%
|
||||
|
||||
Code blocks, URLs, JSON and structured data are **always protected** by the preservation engine. Auto-trigger compression by token threshold, or assign a compression pipeline per routing combo.
|
||||
|
||||
📖 [`COMPRESSION_GUIDE.md`](docs/compression/COMPRESSION_GUIDE.md) · [`RTK_COMPRESSION.md`](docs/compression/RTK_COMPRESSION.md) · [`COMPRESSION_ENGINES.md`](docs/compression/COMPRESSION_ENGINES.md)
|
||||
📖 [`COMPRESSION_GUIDE.md`](docs/COMPRESSION_GUIDE.md) · [`RTK_COMPRESSION.md`](docs/RTK_COMPRESSION.md) · [`COMPRESSION_ENGINES.md`](docs/COMPRESSION_ENGINES.md)
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -523,19 +479,6 @@ curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY"
|
||||
|
||||
You should see your connected models listed. 🎉 That's it — start coding, and OmniRoute auto-routes & falls back for you.
|
||||
|
||||
If your client cannot send custom headers, OmniRoute also exposes tokenized compatibility aliases:
|
||||
|
||||
```txt
|
||||
OpenAI catalog: http://localhost:20128/vscode/YOUR_KEY/
|
||||
OpenAI models: http://localhost:20128/vscode/YOUR_KEY/models
|
||||
OpenAI chat: http://localhost:20128/vscode/YOUR_KEY/chat/completions
|
||||
OpenAI responses: http://localhost:20128/vscode/YOUR_KEY/responses
|
||||
Ollama chat: http://localhost:20128/vscode/YOUR_KEY/api/chat
|
||||
Ollama tags: http://localhost:20128/vscode/YOUR_KEY/api/tags
|
||||
```
|
||||
|
||||
Use these only for clients that cannot attach `Authorization: Bearer ...`. Header auth remains the preferred mode.
|
||||
|
||||
<br/>
|
||||
|
||||
## 📦 More install methods — Docker, source, pnpm, Arch</b></summary>
|
||||
@@ -566,34 +509,7 @@ pnpm install -g omniroute && pnpm approve-builds -g && omniroute
|
||||
yay -S omniroute-bin && systemctl --user enable --now omniroute.service
|
||||
```
|
||||
|
||||
**🔧 Nix (Flake)**
|
||||
|
||||
```bash
|
||||
# Using Nix flakes
|
||||
nix develop
|
||||
npm run dev
|
||||
|
||||
# Or using devbox
|
||||
devbox run npm run dev
|
||||
```
|
||||
|
||||
📖 [Docker Guide](docs/guides/DOCKER_GUIDE.md) — Compose profiles, Caddy HTTPS, Cloudflare tunnels.
|
||||
|
||||
**🦭 Podman**
|
||||
|
||||
```bash
|
||||
# 1. Build the image
|
||||
podman build --target runner-base -t omniroute:base .
|
||||
|
||||
# 2. Fix data directory permissions for rootless Podman
|
||||
mkdir -p data && podman unshare chown 1000:1000 ./data
|
||||
|
||||
# 3. Set runtime in .env, then run (see contrib/podman/ for Quadlet)
|
||||
echo "CONTAINER_HOST=podman" >> .env
|
||||
podman compose --profile base up -d
|
||||
```
|
||||
|
||||
📖 [Podman Guide](contrib/podman/README.md) — Quadlet setup, podman-compose, Quadlet.
|
||||
📖 [Docker Guide](docs/DOCKER_GUIDE.md) — Compose profiles, Caddy HTTPS, Cloudflare tunnels.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -664,7 +580,7 @@ podman compose --profile base up -d
|
||||
|
||||
> 💡 The dashboard "cost" is a **savings tracker**, not a bill — OmniRoute never charges you. A "$290 total cost" using free models means **$290 saved**.
|
||||
|
||||
📖 Complete free directory → [`docs/reference/FREE_TIERS.md`](docs/reference/FREE_TIERS.md) — 25+ providers, quotas, base URLs.
|
||||
📖 Complete free directory → [`docs/FREE_TIERS.md`](docs/FREE_TIERS.md) — 25+ providers, quotas, base URLs.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -700,7 +616,7 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
- **🆓 1proxy marketplace** — hundreds of free validated proxies, quality scores, auto-rotation
|
||||
- **Anti-detection** — TLS fingerprint spoofing (`wreq-js`), CLI fingerprint matching, proxy IP preservation
|
||||
|
||||
📖 [`docs/ops/PROXY_GUIDE.md`](docs/ops/PROXY_GUIDE.md)
|
||||
📖 [`docs/PROXY_GUIDE.md`](docs/PROXY_GUIDE.md)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -715,7 +631,7 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
**Quality & Ops:** built-in **Evals** (golden-set: exact/contains/regex/custom) · guardrails (PII, injection, vision) · health dashboard · p50/p95/p99 telemetry · webhooks · compliance audit.
|
||||
**AI Agent Skills:** drop-in markdown manifests — point any agent at `skills/omniroute/SKILL.md`. 10 skills available.
|
||||
|
||||
📖 [MCP Server](open-sse/mcp-server/README.md) · [A2A Server](src/lib/a2a/README.md) · [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md) · [Features Gallery](docs/guides/FEATURES.md)
|
||||
📖 [MCP Server](open-sse/mcp-server/README.md) · [A2A Server](src/lib/a2a/README.md) · [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md) · [Features Gallery](docs/FEATURES.md)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -735,7 +651,7 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
**Will compression hurt quality?** No — it only compresses the **input**; code, URLs, JSON are always protected.
|
||||
**Does it work where AI is blocked?** Yes — 3-level proxy + 1proxy marketplace reach all 177 providers.
|
||||
|
||||
📖 [User Guide](docs/guides/USER_GUIDE.md) · [API Reference](docs/reference/API_REFERENCE.md) · [Environment Config](docs/reference/ENVIRONMENT.md)
|
||||
📖 [User Guide](docs/USER_GUIDE.md) · [API Reference](docs/API_REFERENCE.md) · [Environment Config](docs/ENVIRONMENT.md)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -753,7 +669,7 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
| Docker SQLite locks | Use `--stop-timeout 40` for clean WAL checkpoint |
|
||||
| Node runtime errors | Use Node `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24 <25` |
|
||||
|
||||
🐛 **Reporting a bug?** Run `npm run system-info` and attach `system-info.txt`. 📖 [`docs/guides/TROUBLESHOOTING.md`](docs/guides/TROUBLESHOOTING.md)
|
||||
🐛 **Reporting a bug?** Run `npm run system-info` and attach `system-info.txt`. 📖 [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -777,7 +693,8 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
|
||||
# 📧 Support & Community
|
||||
|
||||
> 💬 **Chat with the community** — Discord, Telegram & WhatsApp (🌍 / 🇧🇷) links are at the [top of this README](#-join-the-community).
|
||||
> 💬 **Join our WhatsApp groups** — get help, share tips, stay updated:
|
||||
> · [**🌍 International**](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) · [**🇧🇷 Português**](https://chat.whatsapp.com/CeGCxdFzqBe5Uki288wOvf)
|
||||
|
||||
- 🌍 **Website**: [omniroute.online](https://omniroute.online)
|
||||
- 🐙 **GitHub**: [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
|
||||
@@ -823,51 +740,50 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
|
||||
| Document | Description |
|
||||
| ------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [User Guide](docs/guides/USER_GUIDE.md) | Providers, combos, CLI integration, deployment |
|
||||
| [Setup Guide](docs/guides/SETUP_GUIDE.md) | Full install methods, CLI tool configs, protocol setup, timeout tuning |
|
||||
| [CLI Tools Guide](docs/reference/CLI-TOOLS.md) | Per-tool setup for Claude Code, Codex, Cursor, Cline, OpenClaw, Kilo, Copilot |
|
||||
| [User Guide](docs/USER_GUIDE.md) | Providers, combos, CLI integration, deployment |
|
||||
| [Setup Guide](docs/SETUP_GUIDE.md) | Full install methods, CLI tool configs, protocol setup, timeout tuning |
|
||||
| [CLI Tools Guide](docs/CLI-TOOLS.md) | Per-tool setup for Claude Code, Codex, Cursor, Cline, OpenClaw, Kilo, Copilot |
|
||||
| [Quick Start](README.md#-quick-start) | 3-step install → connect → configure |
|
||||
|
||||
### 🔧 Operations & Deployment
|
||||
|
||||
| Document | Description |
|
||||
| ---------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| [Docker Guide](docs/guides/DOCKER_GUIDE.md) | Docker run, Compose profiles, Caddy HTTPS, tunnels, image tags |
|
||||
| [Podman Guide](contrib/podman/README.md) | Quadlet systemd integration, podman-compose, SELinux |
|
||||
| [VM Deployment](docs/ops/VM_DEPLOYMENT_GUIDE.md) | Complete guide: VM + nginx + Cloudflare setup |
|
||||
| [Fly.io Deployment](docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) | Deploy to Fly.io with persistent storage |
|
||||
| [Termux Guide](docs/guides/TERMUX_GUIDE.md) | Run OmniRoute on Android via Termux |
|
||||
| [PWA Guide](docs/guides/PWA_GUIDE.md) | Progressive Web App install, caching, architecture |
|
||||
| [Uninstall Guide](docs/guides/UNINSTALL.md) | Clean removal for all install methods |
|
||||
| [Environment Config](docs/reference/ENVIRONMENT.md) | Complete `.env` variables and references |
|
||||
| [Docker Guide](docs/DOCKER_GUIDE.md) | Docker run, Compose profiles, Caddy HTTPS, tunnels, image tags |
|
||||
| [VM Deployment](docs/VM_DEPLOYMENT_GUIDE.md) | Complete guide: VM + nginx + Cloudflare setup |
|
||||
| [Fly.io Deployment](docs/FLY_IO_DEPLOYMENT_GUIDE.md) | Deploy to Fly.io with persistent storage |
|
||||
| [Termux Guide](docs/TERMUX_GUIDE.md) | Run OmniRoute on Android via Termux |
|
||||
| [PWA Guide](docs/PWA_GUIDE.md) | Progressive Web App install, caching, architecture |
|
||||
| [Uninstall Guide](docs/UNINSTALL.md) | Clean removal for all install methods |
|
||||
| [Environment Config](docs/ENVIRONMENT.md) | Complete `.env` variables and references |
|
||||
|
||||
### 🧠 Features & Architecture
|
||||
|
||||
| Document | Description |
|
||||
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [Architecture](docs/architecture/ARCHITECTURE.md) | System architecture, data flow, and internals |
|
||||
| [Compression Guide](docs/compression/COMPRESSION_GUIDE.md) | 7-option pipeline: off / lite / standard / aggressive / ultra / RTK / stacked |
|
||||
| [RTK Compression](docs/compression/RTK_COMPRESSION.md) | Command-output compression, filters, trust, verify, raw-output recovery |
|
||||
| [Compression Engines](docs/compression/COMPRESSION_ENGINES.md) | Caveman, RTK, stacked pipelines, dashboard/API/MCP surfaces |
|
||||
| [Compression Rules Format](docs/compression/COMPRESSION_RULES_FORMAT.md) | JSON rule-pack schemas for Caveman and RTK filters |
|
||||
| [Compression Language Packs](docs/compression/COMPRESSION_LANGUAGE_PACKS.md) | Language detection and Caveman rule-pack authoring |
|
||||
| [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md) | Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing |
|
||||
| [Auto-Combo Engine](docs/routing/AUTO-COMBO.md) | 6-factor scoring, mode packs, self-healing |
|
||||
| [Proxy Guide](docs/ops/PROXY_GUIDE.md) | 3-level proxy system, 1proxy marketplace, registry CRUD |
|
||||
| [Free Tiers](docs/reference/FREE_TIERS.md) | 25+ free API providers consolidated directory |
|
||||
| [Features Gallery](docs/guides/FEATURES.md) | Visual dashboard tour with screenshots |
|
||||
| [Codebase Documentation](docs/architecture/CODEBASE_DOCUMENTATION.md) | Beginner-friendly codebase walkthrough |
|
||||
| [Architecture](docs/ARCHITECTURE.md) | System architecture, data flow, and internals |
|
||||
| [Compression Guide](docs/COMPRESSION_GUIDE.md) | 7-option pipeline: off / lite / standard / aggressive / ultra / RTK / stacked |
|
||||
| [RTK Compression](docs/RTK_COMPRESSION.md) | Command-output compression, filters, trust, verify, raw-output recovery |
|
||||
| [Compression Engines](docs/COMPRESSION_ENGINES.md) | Caveman, RTK, stacked pipelines, dashboard/API/MCP surfaces |
|
||||
| [Compression Rules Format](docs/COMPRESSION_RULES_FORMAT.md) | JSON rule-pack schemas for Caveman and RTK filters |
|
||||
| [Compression Language Packs](docs/COMPRESSION_LANGUAGE_PACKS.md) | Language detection and Caveman rule-pack authoring |
|
||||
| [Resilience Guide](docs/RESILIENCE_GUIDE.md) | Circuit breakers, cooldowns, queue, anti-thundering herd, TLS spoofing |
|
||||
| [Auto-Combo Engine](docs/AUTO-COMBO.md) | 6-factor scoring, mode packs, self-healing |
|
||||
| [Proxy Guide](docs/PROXY_GUIDE.md) | 3-level proxy system, 1proxy marketplace, registry CRUD |
|
||||
| [Free Tiers](docs/FREE_TIERS.md) | 25+ free API providers consolidated directory |
|
||||
| [Features Gallery](docs/FEATURES.md) | Visual dashboard tour with screenshots |
|
||||
| [Codebase Documentation](docs/CODEBASE_DOCUMENTATION.md) | Beginner-friendly codebase walkthrough |
|
||||
|
||||
### 🤖 Protocols & APIs
|
||||
|
||||
| Document | Description |
|
||||
| ------------------------------------------- | --------------------------------------------------- |
|
||||
| [API Reference](docs/reference/API_REFERENCE.md) | All endpoints with examples |
|
||||
| [OpenAPI Spec](docs/reference/openapi.yaml) | OpenAPI 3.0 specification |
|
||||
| [API Reference](docs/API_REFERENCE.md) | All endpoints with examples |
|
||||
| [OpenAPI Spec](docs/openapi.yaml) | OpenAPI 3.0 specification |
|
||||
| [MCP Server](open-sse/mcp-server/README.md) | 29 MCP tools, IDE configs, Python/TS/Go clients |
|
||||
| [MCP Server Guide](docs/frameworks/MCP-SERVER.md) | MCP installation, transports, and tool reference |
|
||||
| [MCP Server Guide](docs/MCP-SERVER.md) | MCP installation, transports, and tool reference |
|
||||
| [A2A Server](src/lib/a2a/README.md) | JSON-RPC 2.0 protocol, skills, streaming, task mgmt |
|
||||
| [A2A Server Guide](docs/frameworks/A2A-SERVER.md) | A2A agent card, tasks, skills, and streaming |
|
||||
| [A2A Server Guide](docs/A2A-SERVER.md) | A2A agent card, tasks, skills, and streaming |
|
||||
|
||||
### 📋 Project & Quality
|
||||
|
||||
@@ -875,9 +791,9 @@ Compression: aggressive (~50%) → double your free quota · Cost: $0/mo
|
||||
| ---------------------------------------------- | ----------------------------------------------- |
|
||||
| [Contributing](CONTRIBUTING.md) | Development setup and guidelines |
|
||||
| [Security Policy](SECURITY.md) | Vulnerability reporting and security practices |
|
||||
| [i18n Guide](docs/guides/I18N.md) | 40+ language support, translation workflow, RTL |
|
||||
| [Release Checklist](docs/ops/RELEASE_CHECKLIST.md) | Pre-release validation steps |
|
||||
| [Coverage Plan](docs/ops/COVERAGE_PLAN.md) | Test coverage strategy and 4,690+ test suite |
|
||||
| [i18n Guide](docs/I18N.md) | 40+ language support, translation workflow, RTL |
|
||||
| [Release Checklist](docs/RELEASE_CHECKLIST.md) | Pre-release validation steps |
|
||||
| [Coverage Plan](docs/COVERAGE_PLAN.md) | Test coverage strategy and 4,690+ test suite |
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -1011,7 +927,7 @@ Special thanks to **[Caveman](https://github.com/JuliusBrussee/caveman)** by **[
|
||||
|
||||
Special thanks to **[RTK - Rust Token Killer](https://github.com/rtk-ai/rtk)** by **[RTK AI](https://github.com/rtk-ai)** — the high-performance command-output compression project whose terminal, build, test, git, and tool-output filtering model inspired OmniRoute's RTK engine, JSON filter DSL, raw-output recovery, and stacked RTK → Caveman compression pipeline.
|
||||
|
||||
Special thanks to **[Troglodita](https://github.com/leninejunior/troglodita)** by **[Lenine Júnior](https://github.com/leninejunior)** — the PT-BR token compression project ("por que gastar muitos tokens quando poucos resolve?") whose Portuguese-native rules power OmniRoute's pt-BR language pack: pleonasm reduction, filler removal tuned for Brazilian Portuguese grammar, and technical abbreviations for the dev BR community.
|
||||
<br/>
|
||||
|
||||
## 📄 License
|
||||
|
||||
@@ -1023,7 +939,7 @@ MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
**[⬆ Back to top](#-omniroute)** · Built with ❤️ for the open-source AI community.
|
||||
|
||||
<sub>OmniRoute v3.8.6 · Node ≥22.22.2 · MIT License · <a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
<sub>OmniRoute v3.8.2 · Node ≥22.22.2 · MIT License · <a href="https://omniroute.online">omniroute.online</a></sub>
|
||||
|
||||
</div>
|
||||
<!-- GitHub Discussions enabled for community Q&A -->
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
# Relatório — Quality Gates, Catraca & Anti-Alucinação no OmniRoute
|
||||
|
||||
> **Data:** 2026-06-09
|
||||
> **Origem:** Auditoria do projeto (5 subagentes Opus em paralelo mapeando todas as pastas exceto `node_modules`/`_references`/`dist`) + análise da transcrição do vídeo *"Qualidade de código"* (Stupid Button Club, 2026-05-04) + pesquisa web 2026 (4 frentes, últimos ~3 meses).
|
||||
> **Companheiro:** Veja [`PLANO-QUALITY-GATES.md`](./PLANO-QUALITY-GATES.md) para o plano de implementação bite-sized (TDD).
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
1. **O OmniRoute já é muito mais maduro** que o projeto "Strawberry" do vídeo: tem CI com 20 jobs, gate de cobertura, ESLint 9 flat, SonarQube, 14 scripts `check-*.mjs` e **uma catraca real já funcionando** (`check-t11-any-budget.mjs` — orçamento de `any` por arquivo que só pode encolher). O vídeo descreve onde queremos chegar; nós já estamos a meio caminho.
|
||||
2. **Mas faltam exatamente as catracas que o vídeo prega.** Não há baseline congelado de métricas, nem gate de **duplicação**, nem de **tamanho de arquivo**, e o gate de cobertura é um **piso fixo** — não uma catraca "nunca piorar".
|
||||
3. **Há derivas (drifts) reais que pegamos na auditoria:** o gate de cobertura **no CI é `40/40/40/40`** (ci.yml:377), não os `60/60/60/60` que o CLAUDE.md anuncia (esse é só o script local). O Husky está **100% comentado** (zero gate local). O SonarQube tem `coverage` e `cpd` **excluídos** (`sonar-project.properties:9-10`) — as duas métricas mais úteis contra "slop" estão desligadas. E 3 scripts `check-*` existem mas **não rodam em lugar nenhum**.
|
||||
4. **Os maiores ímãs de alucinação são estruturais:** o split de provider em 3 arquivos gigantes em 2 workspaces (`providers.ts` ↔ `providerRegistry.ts` ↔ `validation.ts`, com contagens que já divergem: 229 ids vs 155 blocos vs N validadores), os 300 paths `fetch("/api/...")` hardcoded sem ligação de compilação com as rotas, e o arquivo de **12.760 linhas** (`providers/[id]/page.tsx`) que nenhum agente consegue segurar em contexto.
|
||||
5. **2026 confirma a tese do vídeo com dados:** GitClear (211M linhas) mostra duplicação crescendo 4–8× na era da IA; o paper SlopCodeBench prova que *instrução de prompt sozinha não impede a degradação* — só gates determinísticos seguram. O ecossistema 2026 tem ferramentas maduras para cada métrica (jscpd v5, knip v6, eslint-plugin-sonarjs v4, osv-scanner, Qlty, Sonar "Clean as You Code"/"AI Code Assurance").
|
||||
6. **A jogada é em camadas:** (a) reativar/reconciliar o que já existe; (b) construir o **motor de catraca** (baseline.json + coletor + comparador, clonando o `any-budget`); (c) adicionar **gates determinísticos anti-alucinação** (provider-consistency, fetch-target, openapi-routes); (d) catraca de **duplicação + tamanho**; (e) catraca de **cobertura** + detecção de test-masking; (f) skill **`/babysit`** com guarda-corpos. Detalhe no plano.
|
||||
|
||||
---
|
||||
|
||||
## 1. O que o vídeo ensina (insights destilados)
|
||||
|
||||
O vídeo é uma fala sem roteiro sobre *qualidade de código no mundo em que a IA escreve ~100% do código*. Pontos centrais:
|
||||
|
||||
| # | Insight | Citação/essência |
|
||||
|---|---------|------------------|
|
||||
| 1 | **O humano virou o gargalo.** | "Eu acabei virando o gargalo da IA. Fazer o babysit das coisas do request básicas é o gargalo. Não consigo entregar 4 tarefas ao mesmo tempo se preciso ler 10.000 linhas/dia." |
|
||||
| 2 | **Quality Gate = portão que a IA tem que passar.** | Todo PR passa por um portão; a IA fica em loop se autocorrigindo até ficar verde, em vez de o humano revisar e pedir refação. |
|
||||
| 3 | **Baseline + Catraca (ratchet).** | "Tu congela o baseline e o repositório só pode melhorar a partir dali ou empatar." A catraca anda num sentido só. |
|
||||
| 4 | **Regra de ouro.** | "Cada PR pode adicionar código, mas não pode aumentar nenhuma das métricas — nem por uma violação, nem por uma linha, nem por 0,1 ponto percentual." |
|
||||
| 5 | **Métricas do baseline.** | Violações de ESLint (483 em 120 arquivos), duplicação de código (2,2% via JSCPD), cobertura (%), arquivos acima do limite de tamanho (19 arquivos; o maior com 4.600 linhas). |
|
||||
| 6 | **Pipeline de CI.** | `npm ci` → `npm audit` (critical=bloqueia / high=avisa) → `npm run lint` → `test:coverage` → **script node de quality gate** que compara métricas atuais vs `baseline.json` e falha em qualquer regressão → comentário no PR + **upload de artefatos** que o agente lê para se autocorrigir. |
|
||||
| 7 | **Artefatos legíveis pelo agente.** | "Não adianta cuspir isso no PR. O agente precisa ter acesso ao que está dando errado." |
|
||||
| 8 | **Babysit skill.** | "Recomendo criar uma skill de babysit": a IA monitora o CI + comentários dos revisores, endereça os comentários e **resolve as conversas** para dar rastreabilidade no GitHub. |
|
||||
| 9 | **Comentários perto do código (legibilidade p/ agente).** | Mudou de ideia: antes era contra comentários ("o código é a documentação"); agora, no mundo de agentes, comentário explicando *o quê* e *por quê* perto do código vale mais que um MD gigante, porque o harness faz `grep` no arquivo e lê o comentário junto. |
|
||||
| 10 | **É "só" colar ferramentas.** | "Não é nada excepcional. Eu só estou colando um monte de ferramentas e chamando de quality gate." Pode-se usar SonarQube ou GitHub Code Quality no lugar do script caseiro. |
|
||||
| 11 | **Por que a IA não faz certo de primeira.** | Modelos top já sabem (foram treinados com os livros), mas são "preguiçosos" porque output imperfeito = mais tokens vendidos. A catraca força o nível. |
|
||||
|
||||
**Tradução para o nosso contexto:** o vídeo descreve um sistema que **já temos em embrião** (o `any-budget` é exatamente a catraca da regra de ouro, só que para uma métrica). O salto é (1) generalizar a catraca para todas as métricas, (2) reconciliar os drifts, e (3) fechar os buracos anti-alucinação que são específicos do nosso tamanho.
|
||||
|
||||
---
|
||||
|
||||
## 2. Onde o OmniRoute está hoje (panorama auditado)
|
||||
|
||||
### 2.1 O que já temos (e o vídeo nem sonha)
|
||||
|
||||
- **CI robusto** (`.github/workflows/ci.yml`, ~25 KB, 20 jobs): lint + audit + cycles + route-validation + any-budget + docs-sync + typecheck (core e noimplicit) + build + package-artifact + electron-smoke + unit (8 shards) + Node 24/26 compat + coverage (8 shards + merge) + SonarQube + e2e (9 shards) + integration + security.
|
||||
- **Catraca real já existente:** `scripts/check/check-t11-any-budget.mjs` — array `{file, maxAny}` (a maioria `0`), strip de comentários, anotações de falso-positivo, `exit 1` em regressão. **É o template exato da catraca do vídeo.**
|
||||
- **14 scripts `check-*.mjs`** (cycles, route-validation, any-budget, docs-sync, docs-counts, env-doc-sync, deprecated-versions, doc-links, cli-i18n, openapi-coverage, openapi-security-tiers, pr-test-policy, node-runtime, test-report-summary) — vários já são *gates de consistência fonte-vs-derivado*, o mesmo padrão que precisamos para anti-alucinação.
|
||||
- **PR test policy:** `check-pr-test-policy.mjs` já força "mudou código de produção ⇒ mudou teste" (diff base...HEAD).
|
||||
- **Cobertura sumarizada + comentada no PR:** `test-report-summary.mjs` + `coverage/coverage-summary.json` + job `coverage-pr-comment` (comentário com marcador `<!-- omniroute-coverage-report -->`). **Isto é exatamente o "artefato legível pelo agente" do vídeo** — já construído.
|
||||
- **Disciplina TDD institucionalizada** (Hard Rule #18: todo fix precisa de teste falha→passa ou validação ao vivo no VPS).
|
||||
- **SonarQube** configurado (job no CI + `sonar-project.properties`).
|
||||
- **Skills agênticas de review** já existem: `/review-prs`, `/review-reviews` (bateria de 8 reviewers + ralph-loop), `/code-review`, `/generate-release` (a única com babysit real de CI, mas de workflows de *release*, não do `ci.yml` do PR).
|
||||
|
||||
### 2.2 O que está DESLIGADO ou inerte ⚠️ (achados da auditoria)
|
||||
|
||||
| Item | Estado | Evidência | Impacto |
|
||||
|------|--------|-----------|---------|
|
||||
| **Husky** | 100% comentado (pre-commit **e** pre-push) | `.husky/pre-commit`, `.husky/pre-push` (todas as linhas com `#`) | Zero enforcement local — lint-staged, docs-sync, any-budget, env-doc-sync, openapi checks e o `test:unit` de pre-push dependem 100% do CI. |
|
||||
| **Gate de cobertura no CI** | **40/40/40/40** (não 60) | `ci.yml:377` `--statements 40 --lines 40 --functions 40 --branches 40` | O comentário do PR renderiza contra 60, o script local gata 60, o RELEASE_CHECKLIST diz 75/70, e o baseline real é ~79–82%. **O único número que bloqueia merge é 40** → gate de cobertura quase banguela. |
|
||||
| **Sonar coverage** | Excluído | `sonar-project.properties:9` `sonar.coverage.exclusions=**/*` | Sonar ignora cobertura de todo arquivo. |
|
||||
| **Sonar CPD (duplicação)** | Excluído | `sonar-project.properties:10` `sonar.cpd.exclusions=**/*` | Sonar não detecta copy-paste — a assinatura nº1 de slop de IA. |
|
||||
| **SonarQube job** | Inerte | `ci.yml` roda scan só se `PR && SONAR_TOKEN != '' && SONAR_HOST_URL != ''`; sem `qualitygate.wait` | Em runs sem secret, escreve "skipped"; mesmo quando roda, nunca falha o build. |
|
||||
| **`npm audit`** | Plano (`moderate`), não escalonado | `package.json:112` `--audit-level=moderate` | O vídeo prega critical=bloqueia / high=avisa. O nosso é um nível único. |
|
||||
| **3 scripts órfãos** | Sem CI nem husky | `check:cli-i18n`, `check:openapi-coverage`, `check:openapi-security-tiers` | Existem, dão `exit 1`, mas não rodam em lugar nenhum (Hard Rules #15/#17 guardadas só por um deles). |
|
||||
| **`typecheck:noimplicit:core`** | `continue-on-error: true` | `ci.yml:45-46` | Warn-only "forward-looking". |
|
||||
|
||||
### 2.3 Hotspots de tamanho (sem gate hoje)
|
||||
|
||||
`64 arquivos > 1000 LOC`, `194 > 500 LOC` (src + open-sse, sem testes). Top:
|
||||
|
||||
| LOC | Arquivo | Risco para edição por IA |
|
||||
|-----|---------|--------------------------|
|
||||
| **12.760** | `src/app/(dashboard)/dashboard/providers/[id]/page.tsx` | God-component: **192 `useState`**, 21 `useEffect`, 87 `fetch()` inline, 34 tipos inline. Nenhuma IA segura em contexto; qualquer edição arrisca apagar estado não relacionado. |
|
||||
| 5.977 | `open-sse/handlers/chatCore.ts` | God-handler: 58 funções, invariantes demais, blast radius alto. |
|
||||
| 4.590 | `open-sse/config/providerRegistry.ts` | Array gigante providers+models+OAuth; mistura `resolvePublicCred()` e literais crus. |
|
||||
| 4.456 | `open-sse/services/combo.ts` | 14 estratégias num `if/else if` sem enum/exhaustiveness — estratégia desconhecida vira no-op silencioso. |
|
||||
| 4.349 | `src/app/(dashboard)/dashboard/combos/page.tsx` | 51 `useState`, mesmo padrão god-component. |
|
||||
| 4.205 | `src/lib/providers/validation.ts` | Mega-função com closures e `SPECIALTY_VALIDATORS` definidos *dentro* da função. |
|
||||
| 3.776 | `open-sse/handlers/imageGeneration.ts` | Branching multi-provider num handler. |
|
||||
| 3.076 | `src/shared/constants/providers.ts` | 229 ids em 27 consts agrupados via `Proxy` — sem lista plana. |
|
||||
| 2.869 | `open-sse/executors/chatgpt-web.ts` | Sessão web reversa; classe só começa na linha 2443. |
|
||||
| 2.278 | `src/app/api/providers/[id]/models/route.ts` | God-route: importa ~30 módulos provider-específicos e ramifica por provider num GET. |
|
||||
|
||||
### 2.4 Ímãs de alucinação (ranqueados, da auditoria)
|
||||
|
||||
1. **Split de provider em 3 arquivos / 2 workspaces (nº1).** Não há lista plana de providers — 229 ids escondidos atrás de 27 consts + merge via `Proxy` (`AI_PROVIDERS`). Uma IA não consegue enumerar "quais providers existem" barato → **inventa ids plausíveis** (variantes `*-web`/`*-cli` inexistentes) ou registra no grupo errado. As três contagens (`providers.ts` 229 ids ↔ `providerRegistry.ts` 155 blocos ↔ `validation.ts` N validadores) **já divergem**, então não há cross-check autoritativo. *(Esse é o tema recorrente das nossas memórias de alucinação — ex.: ids inventados, modelos inexistentes.)*
|
||||
2. **300 paths `fetch("/api/...")` hardcoded** no dashboard (659 call sites), sem client tipado. Refatore uma rota e os call sites apodrecem silenciosamente; uma IA editando a UI inventa rota (`/api/providers/[id]/refresh`) ou assume `res.error.message` numa rota que devolve `{error:"..."}`. Sem ligação de símbolo entre os 659 call sites e os 488 `route.ts`.
|
||||
3. **Dual chat stack (armadilha documentada).** Seleção/fallback de conta vive em `src/sse/` (não `open-sse/`). Uma IA pedida para "consertar fallback de conta" edita `open-sse/handlers/chatCore.ts` (errado) em vez de `src/sse/services/auth.ts`. Nada no código cruza os dois stacks. *(Já erramos um diagnóstico público por isso.)*
|
||||
4. **Estratégias de combo inventadas.** 14 nomes reais soterrados num `if/else` de strings (sem enum exportado) → IA inventa nomes plausíveis-mas-falsos (`"latency-optimized"`, `"failover"`) que passam no typecheck como string e viram no-op.
|
||||
5. **Métodos de executor inventados.** O padrão real é "sobrescreve `execute()` inteiro" (48/50 executors), sem hooks documentados → IA inventa `buildRequest`/`parseChunk`/`mapError` que não existem em `BaseExecutor`.
|
||||
6. **Helpers de erro inventados** + queda para `err.message` cru (viola Rule #12) quando o helper inventado "falha"; 5 web executors hoje **não importam helper nenhum**.
|
||||
7. **AGENTS.md de DB defasado:** documenta 21 migrations / 22 módulos quando o real é **94 / 75** — uma IA lendo isso acredita em conjuntos de tabelas/módulos que não existem mais.
|
||||
8. **Route-guard omitido:** rotas novas spawn-capazes (`/api/services/`, `/api/mcp/`) devem entrar em `LOCAL_ONLY_API_PREFIXES`; a convenção está só no CLAUDE.md, não num teste que a IA veja (parcialmente coberta por 1 script órfão).
|
||||
|
||||
---
|
||||
|
||||
## 3. Gap analysis — modelo do vídeo vs OmniRoute
|
||||
|
||||
| Métrica/peça do vídeo | OmniRoute hoje | Gap |
|
||||
|-----------------------|----------------|-----|
|
||||
| `npm ci` determinístico | ✅ em todos os 14 jobs | — |
|
||||
| `npm audit` critical=bloqueia / high=avisa | ⚠️ `--audit-level=moderate` (nível único) | **Escalonar** em dois invokes |
|
||||
| `lint` | ✅ bloqueante | — |
|
||||
| `test` + cobertura | ✅ mas piso **40** no CI (drift) | **Reconciliar** p/ baseline real + catraca |
|
||||
| **Contagem de ESLint congelada** | ❌ (lint é 0-erros, mas warnings livres) | **Construir** (ratchet de violações) |
|
||||
| **Duplicação % (JSCPD)** | ❌ (Sonar CPD excluído, sem jscpd) | **Construir** (jscpd + catraca) |
|
||||
| **Limite de tamanho de arquivo** | ❌ (sem `max-lines`, sem script) | **Construir** (ESLint max-lines + catraca, freeze dos 64) |
|
||||
| **`baseline.json` congelado** | ❌ (nenhum baseline de métricas no repo) | **Construir** (o coração da catraca) |
|
||||
| **Script comparador (regra de ouro)** | 🟡 existe **para `any`** (`any-budget`) | **Generalizar** p/ todas as métricas |
|
||||
| **Sumário markdown + artefatos p/ o agente** | ✅ (coverage summary + PR comment + artifact) | **Reusar** wholesale |
|
||||
| **Babysit skill (monitora CI + resolve conversas)** | 🟡 `review-prs`/`review-reviews`/`generate-release` parciais; nenhuma resolve threads do PR nem loopa no `ci.yml` | **Construir** `/babysit` |
|
||||
| **Comentários perto do código p/ legibilidade de agente** | 🟡 `routeGuard.ts` é exemplar; resto irregular | **Padrão cultural** (Karpathy/guidelines) |
|
||||
|
||||
---
|
||||
|
||||
## 4. O que o mundo faz em 2026 (pesquisa, últimos ~3 meses)
|
||||
|
||||
> Todas as fontes abaixo vêm com URL + data nas seções de origem (ver §7). Onde a pesquisa **não conseguiu confirmar** algo de fonte primária, está marcado **[não-verificado]** — honestidade de engenharia.
|
||||
|
||||
### 4.1 Catraca / baseline-freeze (a "catraca" do vídeo)
|
||||
|
||||
- **betterer** — o tool canônico de ratchet (snapshot de métrica → `.betterer.results`; CI falha se piora, auto-atualiza se melhora). **[caveat]** Baixa velocidade: último commit no `master` em **ago/2025**, releases vazias no GitHub. Viável, mas **não** apostar como peça load-bearing de longo prazo.
|
||||
- **eslint-formatter-ratchet** — formatter que congela contagem de violações de ESLint. **Ativamente mantido** (commit 2026-03-17). Mais estreito (só ESLint) mas é a trajetória oposta ao betterer.
|
||||
- **SonarQube "Clean as You Code" (new-code conditions)** — o padrão baseline-freeze mais maduro: o quality gate aplica condições **só ao código novo** (branch de referência), grandfathering do legado. Atual.
|
||||
- **SonarQube "AI Code Assurance" (2026.1.0)** — gate específico para código gerado por IA (tag o projeto → workflow de assurance + "Sonar way for AI Code" mais estrito). **[não-verificado]** se *bloqueia* o PR (docs canônicas deram 404; descrito como "enforced quality gate" mas mecânica de bloqueio não confirmada em fonte única).
|
||||
- **Qlty CLI (qlty.sh)** — o produto 2026 mais aderente: CLI Rust **OSS e grátis** (v0.630.0, 2026-05-08) que agrega 70+ analisadores; tem **Baseline analysis** (= a catraca), **Quality Gates** com veredito go/no-go e coverage gates. **[caveat]** `qlty metrics` (a tabela LOC/complexidade) **não tem flag JSON** — só `qlty check --sarif`/`qlty smells --sarif`; o ratchet de tamanho/complexidade por arquivo ainda precisa do JSON do ESLint.
|
||||
- **Code Climate Quality → Qlty** — a marca clássica de ratchet virou empresa separada (Qlty, nov/2024). Write-ups antigos de "Code Climate" = Qlty hoje.
|
||||
|
||||
### 4.2 Ferramentas de métrica por tipo (todas com JSON p/ alimentar a catraca)
|
||||
|
||||
| Métrica | Tool 2026 | Comando JSON | Status |
|
||||
|---------|-----------|--------------|--------|
|
||||
| Duplicação | **jscpd v5** (reescrita Rust) | `jscpd --reporters json` (ou `sarif`) | Muito ativo (v5.0.4, 2026-06-08). **[caveat]** schema JSON v4→v5 não confirmado — verificar no install. |
|
||||
| Tamanho/fn-length/ciclomática | **ESLint core** (`max-lines`, `max-lines-per-function`, `complexity`) | `eslint --format json` | Built-in ESLint 9 |
|
||||
| Complexidade cognitiva | **eslint-plugin-sonarjs** (`sonarjs/cognitive-complexity`) | `eslint --format json` | Mantido (v4.0.3, 2026-04-16; agora no monorepo SonarJS — o repo standalone foi arquivado, mas o pacote está vivo). **[caveat]** README das rules deu 404; presença de S3776 em v4 é alta-confiança mas confirmar no install. |
|
||||
| Dead code / unused exports / unused deps | **knip** (vence ts-prune **arquivado** + depcheck **arquivado**) | `knip --reporter json` | Muito ativo (v6.16.1, 2026-06-06) |
|
||||
| Ciclos | **check-cycles.mjs** (já temos) + **dpdm** opcional | `dpdm --circular --output deps.json` | dpdm ativo (v4.2.0, 2026-05-09); madge estagnado |
|
||||
| Vulnerabilidades | **osv-scanner** (Google/OSV) | `osv-scanner --format json` | Muito ativo (push 2026-06-08) |
|
||||
| Política de lockfile (gate, não métrica) | **lockfile-lint** | `lockfile-lint --validate-https --validate-integrity` | Mantido (v5.0.0, 2026-01-25) |
|
||||
|
||||
> **Realidade do ratchet:** não existe tool único 2026 que emita *todas* as métricas como um JSON limpo. O padrão robusto é **N tools que emitem JSON + um reducer Node** que monta `metrics-summary.json` + o comparador que falha só em regressão (exatamente o que o `any-budget` já faz para uma métrica).
|
||||
|
||||
### 4.3 Anti-alucinação (2026)
|
||||
|
||||
- **LSP-in-the-loop / `agent-lsp` (MCP)** — servidor MCP que dá ao agente fatos verificáveis do language server (definições, referências, tipos, diagnostics, `blast_radius`) e `preview_edit` antes de escrever. Funciona com Claude Code. **Fit alto:** vira "símbolo inventado" de catch-de-review para *impossibilidade-no-edit*. (v0.13.0, 2026-06-04 — pequeno mas ativo.)
|
||||
- **Slopsquatting / pacotes alucinados** — **CSA Research Note (2026-04-19):** **19,7%** de 2,23M amostras de código IA continham nomes de pacote alucinados; 205k nomes fabricados únicos; **43%** reaparecem em re-runs (registráveis por atacantes). Defesas: **allowlist** de deps para agentes, **registry existence check** antes de instalar, **age-cooldown** (24–72h), lockfile-exact, scripts de install desabilitados. **Fit alto:** novo `check-deps.mjs`.
|
||||
- **Semcheck** (v1.2.1, fev/2026) — CLI que usa LLM para verificar que a implementação bate com o spec/doc, via `semcheck.yaml` ligando doc↔código; roda em pre-commit e Actions com `fail-on-issues`. Feito para pegar **"docs que descrevem features não implementadas"**. **Fit muito alto** para nossos incidentes recorrentes de docs alucinadas — porém é fuzzy (LLM); pareie com checks determinísticos.
|
||||
- **OpenAPI drift determinístico** — check que toda `path` do `openapi.yaml` resolve para um `route.ts` real (e vice-versa). Rápido, sem LLM, pega "endpoint inventado". **Fit alto** para `docs/reference/openapi.yaml`.
|
||||
- **Skill `verify` / `verification-before-completion`** (já no nosso ambiente) — "evidence before assertions": o veredito PASS/FAIL repousa **só** no que o app rodando demonstrou; rejeita "rodei os testes" como prova. **Fit altíssimo:** é a formalização da nossa Hard Rule #18 — exigir o *output literal* do comando colado no PR ("tool receipt").
|
||||
- **Adversarial review (críticos de sessão fresca)** — agentes Skeptic/Architect/Minimalist leem o diff *contra o spec* ("o autor está comprometido — vai racionalizar"); símbolos/APIs inventados viram violação de spec. Mapeia no nosso `/review-reviews`.
|
||||
- **SlopCodeBench (arXiv 2603.24755, ~mai/2026)** — sem mitigação, erosão estrutural aumentou em **77%** das trajetórias; o código de agente acumula verbosidade ~7× e erosão ~5× mais rápido que repos humanos. **Mitigações só-de-prompt ("anti-slop", "plan-first") melhoram o início mas NÃO param a degradação iterativa.** → **justificativa empírica** de que precisamos de gates determinísticos, não instrução.
|
||||
- **GPT-5.5 System Card (2026-04-23)** — figuras oficiais são modestas (23% mais provável de acerto factual; 3% menos erros num set propenso). O headline de **"queda de 60% em alucinação / 88,7% SWE-bench" é imprensa secundária [não-verificado]**, não a seção de factualidade do system card. Upgrade de modelo ajuda na margem, não substitui gate.
|
||||
|
||||
### 4.4 Babysit loops (2026)
|
||||
|
||||
- **Claude Code "auto-fix in the cloud"** (Anthropic, lançado 2026-03-27): "observa seus PRs na nuvem, resolvendo falhas de CI e comentários de review automaticamente; empurra fixes quando claro, pergunta quando ambíguo." Não auto-mergeia.
|
||||
- **Devin Autofix** (2026-02-10): auto-conserta comentários de review + lint/CI; endereça comentários de bots, mas deixa julgamento humano nas conversas humanas.
|
||||
- **CodeRabbit Autofix** (early access abr/2026): coleta o bloco **"Prompt for AI Agents"** de cada comentário, aplica fix, roda build-verification; **nada mergeia automaticamente**.
|
||||
- **Greptile `greploop` + skill `check-pr`** (MIT) — "dispara review → conserta comentários → re-review até 5/5 de confiança e zero comentários". **Template quase-exato** para a nossa `/babysit`.
|
||||
- **Claude `claude ultrareview <PR#> --json`** (subcomando não-interativo, research preview): bloqueia até terminar, `exit 0/1`, payload de bugs verificados parseável. **Não auto-inicia** e custa $5–20/run → usar atrás de label, não em todo push. (O `/code-review ultra` local com `--fix` é o loop interno de custo-zero.)
|
||||
- **Resolver threads de review:** não há comando `gh` nativo (cli/cli#12419). Padrão de 2 passos GraphQL: `reviewThreads(first:50){nodes{id isResolved...}}` → `mutation { resolveReviewThread(input:{threadId}) }`, respondendo antes com o SHA do commit via REST.
|
||||
- **Guarda-corpos (críticos):**
|
||||
- **Snyk Agent Fix field test (2026): ~5,3% de regressão** ("1 em 19 fixes auto-mergeados introduz problema novo") → **forte argumento contra auto-merge**.
|
||||
- **Token burn:** loops ingênuos realimentam a conversa crescente → prompt incha → alucina do próprio histórico. Uber capou gasto em **$1.500/mês/dev/tool** (abr/2026). Mitigação: **max-iterations**, time limit, idle-exit.
|
||||
- **Test-masking:** o babysit **NÃO** pode enfraquecer/remover asserts para ficar verde (= nossa Rule #18 + memória "trust but verify"). Revisão humana fica nos limites arquiteturais (interface/schema/cross-service).
|
||||
- **Audit trail:** deixar rastro humano-legível (qual fix endereçou o quê, qual gate satisfez, quais conversas resolveu) — nunca um verde silencioso. Reforça o guard de prompt-injection sobre os *corpos de comentário* que o agente ingere (21% dos reviews do ICLR 2026 eram IA; injeção embutida em código é vetor real).
|
||||
|
||||
---
|
||||
|
||||
## 5. Recomendações (ranqueadas) → ver o PLANO
|
||||
|
||||
> Build-vs-buy: **construir in-repo** os gates determinísticos (zero SaaS, dados não saem da box, reusa o harness `check-*.mjs`). **Avaliar Qlty CLI** depois, se quisermos consolidar N scripts num tool. **Não** depender de CodeRabbit/Greptile/Diamond como *o* gate de "não-piorar-métrica" — são opiniões de LLM, não contadores determinísticos.
|
||||
|
||||
**Fase 0 — Reativar & reconciliar (quick wins, sem tooling novo):**
|
||||
1. Reativar pre-commit barato do Husky (lint-staged + docs-sync + any-budget).
|
||||
2. Reconciliar o gate de cobertura: subir o CI de 40 → baseline real (com headroom) e alinhar os 4 lugares que divergem.
|
||||
3. Escalonar `npm audit` (critical=bloqueia / high=avisa).
|
||||
4. Plugar os 3 scripts órfãos (`cli-i18n`, `openapi-coverage`, `openapi-security-tiers`) no CI.
|
||||
|
||||
**Fase 1 — Motor de catraca (o coração):**
|
||||
5. `quality-baseline.json` commitado + `collect-metrics.mjs` (coletor) + `check-quality-ratchet.mjs` (comparador, clone do `any-budget`) + job de CI + artefato + comentário no PR (clone do `coverage-pr-comment`).
|
||||
|
||||
**Fase 2 — Gates determinísticos anti-alucinação:**
|
||||
6. `check-provider-consistency.mjs` (o ímã nº1), `check-fetch-targets.mjs`, `check-openapi-routes.mjs`, allow-list de estratégias/translators/executors, lint Rule #11/#12, `check-deps.mjs` (slopsquatting).
|
||||
|
||||
**Fase 3 — Catraca de duplicação + tamanho (mata-slop):**
|
||||
7. jscpd + ESLint `max-lines`/`max-lines-per-function`/`complexity` + `sonarjs/cognitive-complexity`, congelando os 64 arquivos grandes (catraca só-pode-encolher).
|
||||
|
||||
**Fase 4 — Catraca de cobertura + anti test-masking:**
|
||||
8. `check-coverage-ratchet.mjs` (cobertura não cai vs baseline) + pisos por módulo crítico + `check-test-masking.mjs` (delta de contagem de asserts em testes alterados).
|
||||
|
||||
**Fase 5 — Skill `/babysit` + evidência + LSP:**
|
||||
9. Skill `/babysit` (gh pr checks + reviewThreads + worktree de fix + resolveReviewThread + loop-até-verde, com guarda-corpos), "evidence-before-assertions" obrigatório no corpo do PR, e (opcional) `agent-lsp` MCP.
|
||||
|
||||
---
|
||||
|
||||
## 6. Riscos & ressalvas (honestidade de engenharia)
|
||||
|
||||
- **Flag-day risk:** ligar qualquer gate num projeto que nunca o teve deixa tudo vermelho. **Toda** catraca aqui é *só-regressão* (baseline congelado), nunca um piso absoluto que exige limpeza imediata — exatamente o ponto do vídeo.
|
||||
- **Custo de IA:** o babysit pode queimar tokens. Guarda-corpos (max-iterations, sem auto-merge, sem editar `.github/workflows/`) são não-negociáveis.
|
||||
- **Ressalvas de pesquisa não-verificadas:** betterer baixa-velocidade; Sonar "AI Code Assurance" bloqueio-de-PR não confirmado; "GPT-5.5 −60% alucinação" é imprensa, não system card; schema JSON do jscpd v5 e S3776 do sonarjs v4 a confirmar no install; `qlty metrics` sem JSON. Nenhuma decisão do plano depende criticamente de um item não-verificado.
|
||||
- **Trust-but-verify:** estes números internos (CI=40, husky off, sonar exclusions, 12.760 LOC, any-budget como catraca) foram **conferidos manualmente** contra os arquivos, não só relatados pelos subagentes.
|
||||
|
||||
---
|
||||
|
||||
## 7. Fontes (consolidadas)
|
||||
|
||||
**Catraca / ratchet:** betterer `github.com/phenomnomnominal/betterer` (último master ago/2025); eslint-formatter-ratchet `github.com/Jmsa/eslint-formatter-ratchet` (commit 2026-03-17); SonarQube Clean as You Code `docs.sonarsource.com/.../clean-as-you-code/about-new-code/`; Sonar AI Code Assurance `sonarsource.com/solutions/ai/ai-code-assurance/` + community thread 2026.1.0; Qlty `github.com/qltysh/qlty` (v0.630.0, 2026-05-08), `docs.qlty.sh`; Code Climate→Qlty `codeclimate.com/legacy/...` (2024-11-11).
|
||||
|
||||
**Métricas:** jscpd `github.com/kucherenko/jscpd` (v5.0.4, 2026-06-08); ESLint v10 `eslint.org/blog/2026/02/eslint-v10.0.0-released/` (2026-02-06); eslint-plugin-sonarjs `npm` (v4.0.3, 2026-04-16) + `github.com/SonarSource/SonarJS`; knip `knip.dev` (v6.16.1, 2026-06-06); dpdm `github.com/acrazing/dpdm` (v4.2.0, 2026-05-09); osv-scanner `google.github.io/osv-scanner` (push 2026-06-08); lockfile-lint `github.com/lirantal/lockfile-lint` (v5.0.0, 2026-01-25); GitClear `gitclear.com/ai_assistant_code_quality_2025_research`.
|
||||
|
||||
**Anti-alucinação:** agent-lsp `github.com/blackwell-systems/agent-lsp` (v0.13.0, 2026-06-04); CSA Slopsquatting `labs.cloudsecurityalliance.org/research/...slopsquatting...20260419...` (2026-04-19); Nesbitt package defenses `nesbitt.io/2026/04/09/...` (2026-04-09); Semcheck `github.com/rejot-dev/semcheck` (v1.2.1, fev/2026); verify skill `github.com/Piebald-AI/claude-code-system-prompts/.../skill-verify-skill.md`; Claude best practices `code.claude.com/docs/en/best-practices`; SlopCodeBench `arxiv.org/pdf/2603.24755`; GPT-5.5 system card `deploymentsafety.openai.com/gpt-5-5` (2026-04-23); adversarial review `asdlc.io/patterns/adversarial-code-review/`; OpenAPI drift `speakeasy.com/blog/openapi-spec-drift-detection`.
|
||||
|
||||
**Babysit:** Claude auto-fix cloud `producthunt.com/products/claude-code-auto-fix-in-the-cloud` (2026-03-27); Devin Autofix `cognition.ai/blog/closing-the-agent-loop-...` (2026-02-10); CodeRabbit Autofix `coderabbit.ai/blog/fix-all-issues-with-ai-agents` (2026-02-19); Greptile skills `github.com/greptileai/skills`; Claude GitHub Actions/Code Review/ultrareview `code.claude.com/docs/en/{github-actions,code-review,ultrareview}`; Nx self-healing `nx.dev/blog/autonomous-ai-workflows-with-nx` (2026-02-03); Snyk Agent Fix 5,3% `safeguard.sh/resources/blog/snyk-agent-fix-autofix-field-test-2026`; resolveReviewThread `nakamasato.medium.com/...` + `github.com/cli/cli/issues/12419`; ICLR 2026 AI review `blog.pebblous.ai/report/iclr-2026-ai-peer-review-crisis`.
|
||||
|
||||
---
|
||||
|
||||
*Relatório gerado a partir de auditoria paralela do código + transcrição do vídeo + pesquisa web 2026. Próximo passo: aprovar o [`PLANO-QUALITY-GATES.md`](./PLANO-QUALITY-GATES.md) e escolher por onde começar (recomendação: Fase 0 → Fase 1).*
|
||||
24
SECURITY.md
24
SECURITY.md
@@ -208,29 +208,6 @@ These rules are enforced by tooling and reviewers:
|
||||
10. **`exec()` / `spawn()` runtime values via the `env` option** — never string-interpolate external paths or untrusted values into shell-passed scripts. Reference: `src/mitm/cert/install.ts::updateNssDatabases`.
|
||||
11. **Prefer secure-by-default libraries** — see [tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults) (Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink). Reach for them before rolling your own.
|
||||
|
||||
## Supply-chain scanner findings (Socket.dev / Snyk / similar)
|
||||
|
||||
The published `omniroute` npm artifact bundles the Next.js `output: "standalone"`
|
||||
build, which means every route handler — including documented privileged
|
||||
features (MITM, Zed import, Cloud Sync, embedded service supervisor) — ends
|
||||
up in `.next/server/*.js` minified chunks. Heuristic supply-chain scanners
|
||||
frequently pattern-match those chunks against malware signatures.
|
||||
|
||||
For each finding category we maintain a per-finding maintainer attestation:
|
||||
|
||||
- **[`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md)** —
|
||||
per-finding map: source file ↔ flagged chunk ↔ behaviour ↔ mitigation
|
||||
applied in v3.8.6.
|
||||
- In-source `SECURITY-AUDITOR-NOTE:` blocks at each flagged function point
|
||||
back to the same document.
|
||||
|
||||
For users whose pipeline cannot relax the alert: build with
|
||||
`OMNIROUTE_BUILD_PROFILE=minimal npm run build`. That replaces the four
|
||||
sensitive modules with stubs that return HTTP 503 `feature-disabled` at
|
||||
runtime, so the privileged code paths are physically absent from the bundle.
|
||||
See [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md)
|
||||
for the publishing recipe.
|
||||
|
||||
## References
|
||||
|
||||
- [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) — authorization pipeline
|
||||
@@ -238,7 +215,6 @@ for the publishing recipe.
|
||||
- [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) — audit log and retention
|
||||
- [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) — **mandatory** pattern for public upstream credentials
|
||||
- [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md) — **mandatory** pattern for error responses
|
||||
- [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md) — maintainer attestation for supply-chain scanner findings
|
||||
- [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) — circuit breaker + cooldown + lockout
|
||||
- [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) — TLS fingerprinting (legal/ethical notice)
|
||||
- [`CLAUDE.md`](CLAUDE.md) — hard rules for AI agents
|
||||
|
||||
@@ -3,37 +3,7 @@ import { apiFetch } from "../api.mjs";
|
||||
import { emit } from "../output.mjs";
|
||||
import { t } from "../i18n.mjs";
|
||||
|
||||
const VALID_TYPES = ["factual", "episodic", "procedural", "semantic"];
|
||||
|
||||
const LEGACY_TYPE_MAP = {
|
||||
user: "factual",
|
||||
feedback: "factual",
|
||||
project: "factual",
|
||||
reference: "factual",
|
||||
};
|
||||
|
||||
/**
|
||||
* Plan 21 Bug#4/D17 fix: remap legacy types in ALL CLI subcommands
|
||||
* (search/list/clear in addition to add), with a stderr warning on remap.
|
||||
* Returns the canonical type, or the original value (which the backend will
|
||||
* 400 on if invalid) — never throws.
|
||||
*/
|
||||
function applyLegacyTypeMap(type) {
|
||||
if (!type) return type;
|
||||
if (Object.prototype.hasOwnProperty.call(LEGACY_TYPE_MAP, type)) {
|
||||
const mapped = LEGACY_TYPE_MAP[type];
|
||||
process.stderr.write(
|
||||
`Warning: legacy type '${type}' is deprecated; using '${mapped}'. Use --type factual|episodic|procedural|semantic.\n`
|
||||
);
|
||||
return mapped;
|
||||
}
|
||||
if (!VALID_TYPES.includes(type)) {
|
||||
process.stderr.write(
|
||||
`Warning: unknown type '${type}'. Valid types: factual, episodic, procedural, semantic.\n`
|
||||
);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
const VALID_TYPES = ["user", "feedback", "project", "reference"];
|
||||
|
||||
function truncate(v, len = 60) {
|
||||
if (v == null) return "-";
|
||||
@@ -83,8 +53,7 @@ async function confirm(question) {
|
||||
export async function runMemorySearch(query, opts, cmd) {
|
||||
const globalOpts = cmd.optsWithGlobals();
|
||||
const params = new URLSearchParams({ q: query, limit: String(opts.limit ?? 20) });
|
||||
const mappedSearchType = applyLegacyTypeMap(opts.type);
|
||||
if (mappedSearchType) params.set("type", mappedSearchType);
|
||||
if (opts.type) params.set("type", opts.type);
|
||||
if (opts.apiKey) params.set("apiKey", opts.apiKey);
|
||||
if (opts.tokenBudget) params.set("tokenBudget", String(opts.tokenBudget));
|
||||
const res = await apiFetch(`/api/memory?${params}`);
|
||||
@@ -103,10 +72,9 @@ export async function runMemoryAdd(opts, cmd) {
|
||||
process.stderr.write("--content or --file required\n");
|
||||
process.exit(2);
|
||||
}
|
||||
const resolvedType = opts.type ? applyLegacyTypeMap(opts.type) : "factual";
|
||||
const body = {
|
||||
content,
|
||||
type: resolvedType,
|
||||
type: opts.type ?? "user",
|
||||
...(opts.metadata ? { metadata: JSON.parse(opts.metadata) } : {}),
|
||||
...(opts.apiKey ? { apiKey: opts.apiKey } : {}),
|
||||
};
|
||||
@@ -126,8 +94,7 @@ export async function runMemoryClear(opts, cmd) {
|
||||
if (!ok) process.exit(0);
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
const mappedClearType = applyLegacyTypeMap(opts.type);
|
||||
if (mappedClearType) params.set("type", mappedClearType);
|
||||
if (opts.type) params.set("type", opts.type);
|
||||
if (opts.olderThan) {
|
||||
const iso = parseDuration(opts.olderThan);
|
||||
if (!iso) {
|
||||
@@ -145,8 +112,7 @@ export async function runMemoryClear(opts, cmd) {
|
||||
export async function runMemoryList(opts, cmd) {
|
||||
const globalOpts = cmd.optsWithGlobals();
|
||||
const params = new URLSearchParams({ limit: String(opts.limit ?? 100) });
|
||||
const mappedListType = applyLegacyTypeMap(opts.type);
|
||||
if (mappedListType) params.set("type", mappedListType);
|
||||
if (opts.type) params.set("type", opts.type);
|
||||
if (opts.apiKey) params.set("apiKey", opts.apiKey);
|
||||
const res = await apiFetch(`/api/memory?${params}`);
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -10,11 +10,7 @@ import { isTermux } from "../../../scripts/build/postinstallSupport.mjs";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(__dirname, "..", "..", "..");
|
||||
// The standalone bundle ships in `dist/` (since the build-output-isolation
|
||||
// refactor). Fall back to the legacy `app/` location so an upgrade over a
|
||||
// partially-replaced install — or a package built before the rename — still
|
||||
// boots. Backward-compatible by design: every deployed runtime keeps its path.
|
||||
const APP_DIR = existsSync(join(ROOT, "dist", "server.js")) ? join(ROOT, "dist") : join(ROOT, "app");
|
||||
const APP_DIR = join(ROOT, "app");
|
||||
|
||||
function parsePort(value, fallback) {
|
||||
const parsed = parseInt(String(value), 10);
|
||||
@@ -25,7 +21,7 @@ export function registerServe(program) {
|
||||
program
|
||||
.command("serve", { isDefault: true })
|
||||
.description(t("serve.description"))
|
||||
.option("--port <port>", t("serve.port"))
|
||||
.option("--port <port>", t("serve.port"), "20128")
|
||||
.option("--no-open", t("serve.no_open"))
|
||||
.option("--daemon", t("serve.daemon"))
|
||||
.option("--log", t("serve.log"))
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { printHeading, printInfo, printSuccess, printError } from "../io.mjs";
|
||||
import { homedir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { t } from "../i18n.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// This file lives at <pkgRoot>/bin/cli/commands/update.mjs — resolve package
|
||||
// paths relative to the script, NOT process.cwd(). On a global npm/brew install
|
||||
// the user's cwd is not the package root, so cwd-relative lookups break (#3295).
|
||||
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const PKG_ROOT = path.resolve(SCRIPT_DIR, "..", "..", "..");
|
||||
const BIN_DIR = path.join(PKG_ROOT, "bin");
|
||||
|
||||
export async function getCurrentVersion() {
|
||||
async function getCurrentVersion() {
|
||||
try {
|
||||
const { readFileSync } = await import("node:fs");
|
||||
const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"));
|
||||
const pkg = JSON.parse(readFileSync(path.join(process.cwd(), "package.json"), "utf-8"));
|
||||
return pkg.version;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -46,12 +38,12 @@ function compareVersions(a, b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function createBackup() {
|
||||
const binPath = BIN_DIR;
|
||||
async function createBackup() {
|
||||
const binPath = path.join(process.cwd(), "bin");
|
||||
const backupDir = path.join(homedir(), ".omniroute", "backups", `omniroute-${Date.now()}`);
|
||||
|
||||
try {
|
||||
const { mkdirSync, cpSync, existsSync } = await import("node:fs");
|
||||
const { mkdirSync, copyFileSync, existsSync } = await import("node:fs");
|
||||
if (!existsSync(binPath)) return null;
|
||||
|
||||
mkdirSync(backupDir, { recursive: true });
|
||||
@@ -59,9 +51,7 @@ export async function createBackup() {
|
||||
for (const f of files) {
|
||||
const src = path.join(binPath, f);
|
||||
if (existsSync(src)) {
|
||||
// cpSync handles both files and directories; the old copyFileSync threw
|
||||
// EISDIR on the "cli" directory, which was swallowed by the catch (#3295).
|
||||
cpSync(src, path.join(backupDir, f), { recursive: true });
|
||||
copyFileSync(src, path.join(backupDir, f));
|
||||
}
|
||||
}
|
||||
return backupDir;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Table from "cli-table3";
|
||||
import { stringify as csvStringify } from "csv-stringify/sync";
|
||||
|
||||
const MASK_RE = /sk-[A-Za-z0-9]{4,}/g;
|
||||
@@ -40,51 +41,6 @@ function formatCell(v, col) {
|
||||
return String(v);
|
||||
}
|
||||
|
||||
const CYAN = "\x1b[36m";
|
||||
const RESET = "\x1b[0m";
|
||||
|
||||
/** Strip ANSI escape sequences so we can measure the visible width of a string. */
|
||||
const stripAnsi = (s) => s.replace(/\x1b\[[\d;]*m/g, "");
|
||||
|
||||
/** Truncate a string to `max` visible chars, appending "…" if trimmed.
|
||||
* ANSI escape codes are excluded from the width count and never split. */
|
||||
function truncateCell(str, max) {
|
||||
const visible = stripAnsi(str);
|
||||
if (visible.length <= max) return str;
|
||||
// Rebuild the string char-by-char, counting only visible chars, stopping at max-1.
|
||||
let count = 0;
|
||||
let result = "";
|
||||
let i = 0;
|
||||
while (i < str.length) {
|
||||
// Detect an ANSI escape sequence starting at position i.
|
||||
if (str[i] === "\x1b" && str[i + 1] === "[") {
|
||||
const end = str.indexOf("m", i + 2);
|
||||
if (end !== -1) {
|
||||
// Include the full escape sequence without counting it as visible width.
|
||||
result += str.slice(i, end + 1);
|
||||
i = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (count >= max - 1) break;
|
||||
result += str[i];
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
// Ensure the reset code is always appended so ANSI color never bleeds.
|
||||
if (str.includes("\x1b[")) {
|
||||
result += RESET;
|
||||
}
|
||||
return result + "…";
|
||||
}
|
||||
|
||||
/** Pad a string to exactly `width` visible chars (left-aligned).
|
||||
* ANSI escape codes are excluded from the padding calculation. */
|
||||
function padCell(str, width) {
|
||||
const visible = stripAnsi(str);
|
||||
return str + " ".repeat(Math.max(0, width - visible.length));
|
||||
}
|
||||
|
||||
function renderTable(rows, schema, opts = {}) {
|
||||
if (rows.length === 0) {
|
||||
process.stdout.write("(empty)\n");
|
||||
@@ -92,43 +48,18 @@ function renderTable(rows, schema, opts = {}) {
|
||||
}
|
||||
const cols = schema || inferSchema(rows[0]);
|
||||
const quiet = opts.quiet === true;
|
||||
|
||||
// Compute column widths: max(header.length, max visible cell length), capped by explicit c.width.
|
||||
const colWidths = cols.map((c) => {
|
||||
const headerLen = c.header.length;
|
||||
const maxData = rows.reduce(
|
||||
(m, row) => Math.max(m, stripAnsi(formatCell(row[c.key], c)).length),
|
||||
0,
|
||||
);
|
||||
const natural = Math.max(headerLen, maxData);
|
||||
return c.width ? Math.max(c.width, 1) : natural;
|
||||
});
|
||||
|
||||
const separator = colWidths.map((w) => "-".repeat(w + 2)).join("-+-");
|
||||
|
||||
const renderRow = (cells, cyan) => {
|
||||
const parts = cells.map((cell, i) => {
|
||||
const truncated = truncateCell(cell, colWidths[i]);
|
||||
const padded = padCell(truncated, colWidths[i]);
|
||||
return cyan ? ` ${CYAN}${padded}${RESET} ` : ` ${padded} `;
|
||||
});
|
||||
return `|${parts.join("|")}|`;
|
||||
const widths = cols.map((c) => c.width || null);
|
||||
const hasWidths = widths.some((w) => w !== null);
|
||||
const tableOpts = {
|
||||
head: quiet ? [] : cols.map((c) => c.header),
|
||||
style: { head: quiet ? [] : ["cyan"] },
|
||||
};
|
||||
|
||||
const lines = [];
|
||||
|
||||
if (!quiet) {
|
||||
lines.push(separator);
|
||||
lines.push(renderRow(cols.map((c) => c.header), true));
|
||||
}
|
||||
lines.push(separator);
|
||||
|
||||
if (hasWidths) tableOpts.colWidths = widths;
|
||||
const table = new Table(tableOpts);
|
||||
for (const row of rows) {
|
||||
lines.push(renderRow(cols.map((c) => formatCell(row[c.key], c)), false));
|
||||
table.push(cols.map((c) => formatCell(row[c.key], c)));
|
||||
}
|
||||
lines.push(separator);
|
||||
|
||||
process.stdout.write(lines.join("\n") + "\n");
|
||||
process.stdout.write(table.toString() + "\n");
|
||||
}
|
||||
|
||||
function renderCsv(rows, schema) {
|
||||
|
||||
@@ -5,6 +5,16 @@ export function detectRestrictedEnvironment() {
|
||||
return { type: "github-codespaces", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
|
||||
if (existsSync("/.dockerenv")) {
|
||||
return { type: "docker", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync("/proc/1/cgroup") && readFileSync("/proc/1/cgroup", "utf8").includes("docker")) {
|
||||
return { type: "docker", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) {
|
||||
return {
|
||||
type: "wsl",
|
||||
@@ -26,16 +36,6 @@ export function detectRestrictedEnvironment() {
|
||||
return { type: "ci", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
|
||||
if (existsSync("/.dockerenv")) {
|
||||
return { type: "docker", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
|
||||
try {
|
||||
if (existsSync("/proc/1/cgroup") && readFileSync("/proc/1/cgroup", "utf8").includes("docker")) {
|
||||
return { type: "docker", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (!process.stdin.isTTY) {
|
||||
return { type: "non-interactive", canOpenBrowser: false, canUseTray: false };
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Decide whether a CLI invocation should provision (generate + persist) the
|
||||
* STORAGE_ENCRYPTION_KEY into DATA_DIR/.env.
|
||||
*
|
||||
* Purely informational invocations must NOT create `~/.omniroute/.env` or write
|
||||
* a key — they never touch encrypted storage. Generating a 32-byte key and a
|
||||
* `.env` file just to print `omniroute --version` (or `--help`) is a surprising
|
||||
* side effect: a read-only command should not mutate the data dir.
|
||||
*
|
||||
* Returns FALSE only for: `--version`/`-V` or `--help`/`-h` anywhere in the args,
|
||||
* and the `help`/`completion` subcommands.
|
||||
*
|
||||
* Returns TRUE for everything else, INCLUDING a bare `omniroute` (no args) — the
|
||||
* `serve` command is `isDefault: true`, so a bare invocation starts the server,
|
||||
* which needs the encryption key. This preserves the #1622 persistence fix
|
||||
* (key generated on first real run and reused across restarts).
|
||||
*
|
||||
* @param {string[]} argv - process.argv (node + script + args).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const INFO_FLAGS = new Set(["-h", "--help", "-V", "--version"]);
|
||||
const INFO_COMMANDS = new Set(["help", "completion"]);
|
||||
|
||||
export function shouldProvisionStorageKey(argv) {
|
||||
const args = Array.isArray(argv) ? argv.slice(2) : [];
|
||||
// Bare `omniroute` runs the default `serve` command → must provision.
|
||||
if (args.length === 0) return true;
|
||||
if (args.some((a) => INFO_FLAGS.has(a))) return false;
|
||||
if (INFO_COMMANDS.has(args[0])) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -17,28 +17,16 @@ import { homedir, platform } from "node:os";
|
||||
import updateNotifier from "update-notifier";
|
||||
import { isNativeBinaryCompatible } from "../scripts/build/native-binary-compat.mjs";
|
||||
import { getNodeRuntimeSupport, getNodeRuntimeWarning } from "./nodeRuntimeSupport.mjs";
|
||||
import { shouldProvisionStorageKey } from "./cli/utils/storageKeyProvision.mjs";
|
||||
|
||||
// Register tsx so dynamic imports of .ts source files (referenced as .js per
|
||||
// TypeScript conventions) resolve correctly. The build never emits .js for
|
||||
// src/lib/cli-helper/, so tsx handles the .ts → .js resolution at runtime.
|
||||
await import("tsx/esm");
|
||||
await import("../open-sse/utils/setupPolyfill.ts");
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const ROOT = join(__dirname, "..");
|
||||
|
||||
// MCP stdio transport uses stdout exclusively for JSON-RPC messages.
|
||||
// Redirect console.log/warn to stderr early (before loadEnvFile and DB init)
|
||||
// so no startup output corrupts the protocol.
|
||||
if (process.argv.includes("--mcp")) {
|
||||
const { Console } = await import("node:console");
|
||||
const stderrConsole = new Console({ stdout: process.stderr, stderr: process.stderr });
|
||||
console.log = stderrConsole.log.bind(stderrConsole);
|
||||
console.warn = stderrConsole.warn.bind(stderrConsole);
|
||||
}
|
||||
|
||||
function loadEnvFile() {
|
||||
const envPaths = [];
|
||||
|
||||
@@ -93,12 +81,7 @@ loadEnvFile();
|
||||
// Generate STORAGE_ENCRYPTION_KEY if not set (persisted to ~/.omniroute/.env)
|
||||
// This ensures the key survives across upgrades and is not regenerated on each install.
|
||||
// See: https://github.com/diegosouzapw/OmniRoute/issues/1622
|
||||
//
|
||||
// Only provision for commands that actually touch encrypted storage. Purely
|
||||
// informational invocations (`--version`, `--help`, `help`) must not create a
|
||||
// key or write ~/.omniroute/.env — running a read-only command should never
|
||||
// mutate the data dir.
|
||||
if (shouldProvisionStorageKey(process.argv)) {
|
||||
{
|
||||
const { randomBytes } = await import("node:crypto");
|
||||
const { existsSync, mkdirSync, readFileSync, writeFileSync } = await import("node:fs");
|
||||
const { join } = await import("node:path");
|
||||
|
||||
0
bin/reset-password.mjs
Executable file → Normal file
0
bin/reset-password.mjs
Executable file → Normal file
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"_comment": "Catraca de complexidade (check-complexity.mjs, ESLint core rules complexity>=15 e max-lines-per-function>80 sobre src+open-sse via eslint.complexity.config.mjs). Conta total de violacoes; so pode cair. --update ratcheta.",
|
||||
"count": 1739
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
# Podman Deployment
|
||||
|
||||
Run OmniRoute with Podman via **Quadlet** (systemd integration) or **podman compose**.
|
||||
|
||||
---
|
||||
|
||||
## Option A: Quadlet (recommended)
|
||||
|
||||
### 1. Build the image
|
||||
|
||||
```bash
|
||||
cd /path/to/omniroute
|
||||
podman build --target runner-base -t omniroute:base .
|
||||
# For web-cookie providers (gemini-web, claude-web, claude-turnstile):
|
||||
podman build --target runner-web -t omniroute:web .
|
||||
# For CLI tool support:
|
||||
podman build --target runner-cli -t omniroute:cli .
|
||||
```
|
||||
|
||||
### 2. Copy Quadlet files to the systemd directory
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/containers/systemd/omniroute
|
||||
cp contrib/podman/*.container ~/.config/containers/systemd/omniroute/
|
||||
cp contrib/podman/*.network ~/.config/containers/systemd/omniroute/
|
||||
cp contrib/podman/*.volume ~/.config/containers/systemd/omniroute/
|
||||
```
|
||||
|
||||
### 3. Mount the project .env for secrets
|
||||
|
||||
Edit `~/.config/containers/systemd/omniroute/omniroute.container` and
|
||||
uncomment/replace the `EnvironmentFile` line with the absolute path to
|
||||
your project `.env`:
|
||||
|
||||
```
|
||||
EnvironmentFile=/home/USER/code/docker/OmniRoute/.env
|
||||
```
|
||||
|
||||
Make sure `CONTAINER_HOST=podman` is set in that `.env`.
|
||||
|
||||
Alternatively, edit the env vars directly in the `.container` file.
|
||||
|
||||
### 4. Reload systemd and start
|
||||
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user start omniroute-redis
|
||||
systemctl --user start omniroute
|
||||
```
|
||||
|
||||
### 5. Verify
|
||||
|
||||
```bash
|
||||
systemctl --user status omniroute
|
||||
curl http://localhost:20128/v1/models
|
||||
```
|
||||
|
||||
To follow logs:
|
||||
|
||||
```bash
|
||||
journalctl --user -u omniroute -f
|
||||
```
|
||||
|
||||
### 6. Enable on boot
|
||||
|
||||
```bash
|
||||
systemctl --user enable omniroute-redis
|
||||
systemctl --user enable omniroute
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Option B: podman compose
|
||||
|
||||
The project's `docker-compose.yml` now works with both Docker and Podman.
|
||||
Just set `CONTAINER_HOST=podman` in `.env` before starting.
|
||||
|
||||
### 1. Prepare the data directory
|
||||
|
||||
Rootless Podman maps container UIDs into a subordinate range. The
|
||||
`node` user (UID 1000) inside the container maps to a different UID
|
||||
on the host, so it cannot write to `./data` owned by your host user.
|
||||
|
||||
Fix the ownership **before** starting:
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
podman unshare chown 1000:1000 ./data
|
||||
```
|
||||
|
||||
### 2. Set the runtime in `.env`
|
||||
|
||||
Make sure `.env` contains:
|
||||
|
||||
```env
|
||||
CONTAINER_HOST=podman
|
||||
```
|
||||
|
||||
### 3. Start
|
||||
|
||||
```bash
|
||||
podman compose --profile base up -d
|
||||
```
|
||||
|
||||
### Profiles
|
||||
|
||||
Same profiles as `docker compose`:
|
||||
|
||||
| Profile | Command |
|
||||
| ------------------------------ | ---------------------------------------------------- |
|
||||
| `base` (no CLIs) | `podman compose --profile base up -d` |
|
||||
| `web` (+Chromium/Playwright) | `podman compose --profile web up -d` |
|
||||
| `cli` (+CLI tools) | `podman compose --profile cli up -d` |
|
||||
| `host` (host-mounted binaries) | `podman compose --profile host up -d` |
|
||||
| `cliproxyapi` (sidecar) | `podman compose --profile cliproxyapi up -d` |
|
||||
|
||||
---
|
||||
|
||||
## How it works
|
||||
|
||||
The `docker-compose.yml` uses fully-qualified image names
|
||||
(`docker.io/library/redis:7-alpine`) and flat variable expansions so it
|
||||
works with both Docker and Podman without a separate compose file.
|
||||
|
||||
The entrypoint script (`check-permissions.sh`) reads `CONTAINER_HOST`
|
||||
from `.env` to give the correct fix instructions:
|
||||
- **docker**: `sudo chown -R ... ./data`
|
||||
- **podman**: `podman unshare chown 1000:1000 ./data`
|
||||
@@ -1,26 +0,0 @@
|
||||
[Unit]
|
||||
Description=OmniRoute Redis (rate-limiter backend)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=docker.io/redis:7-alpine
|
||||
ContainerName=omniroute-redis
|
||||
|
||||
Network=omniroute.network
|
||||
NetworkAlias=redis
|
||||
|
||||
Volume=omniroute-redis.volume:/data
|
||||
|
||||
Exec=redis-server --save 60 1 --loglevel warning
|
||||
|
||||
HealthCmd=/bin/sh -c "redis-cli ping | grep -q PONG"
|
||||
HealthInterval=10s
|
||||
HealthRetries=3
|
||||
HealthTimeout=5s
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,3 +0,0 @@
|
||||
# Persistent Redis data
|
||||
[Volume]
|
||||
Label=omniroute
|
||||
@@ -1,50 +0,0 @@
|
||||
[Unit]
|
||||
Description=OmniRoute — Unified AI Proxy
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Requires=omniroute-redis.service
|
||||
After=omniroute-redis.service
|
||||
|
||||
[Container]
|
||||
# Use this image if your building locally
|
||||
Image=localhost/omniroute:base
|
||||
# Use this image if you are pulling from Docker Hub
|
||||
# Image=diegosouzapw/omniroute:base
|
||||
|
||||
ContainerName=omniroute
|
||||
|
||||
Network=omniroute.network
|
||||
PublishPort=20128:20128
|
||||
PublishPort=20129:20129
|
||||
|
||||
Volume=omniroute.volume:/app/data:Z
|
||||
|
||||
# Use a bind mount if you need direct host access to the data:
|
||||
# Volume=/home/USER/code/docker/OmniRoute/data:/app/data:Z
|
||||
|
||||
Environment=DATA_DIR=/app/data
|
||||
Environment=PORT=20128
|
||||
Environment=DASHBOARD_PORT=20128
|
||||
Environment=API_PORT=20129
|
||||
Environment=API_HOST=0.0.0.0
|
||||
Environment=REDIS_URL=redis://redis:6379
|
||||
Environment=JWT_SECRET=change-me-to-a-random-base64-string
|
||||
Environment=API_KEY_SECRET=change-me-to-a-random-base64-string
|
||||
Environment=INITIAL_PASSWORD=change-me-to-a-random-hex-string
|
||||
Environment=NODE_ENV=production
|
||||
Environment=REQUIRE_API_KEY=true
|
||||
|
||||
# Load additional secrets (API keys, OAuth creds) from the project .env:
|
||||
# EnvironmentFile=%h/code/docker/OmniRoute/.env
|
||||
|
||||
HealthCmd=node /app/healthcheck.mjs
|
||||
HealthInterval=30s
|
||||
HealthRetries=3
|
||||
HealthTimeout=5s
|
||||
HealthStartPeriod=15s
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,2 +0,0 @@
|
||||
[Network]
|
||||
Label=omniroute
|
||||
@@ -1,3 +0,0 @@
|
||||
# OmniRoute persistent data (SQLite DB, logs, backups)
|
||||
[Volume]
|
||||
Label=omniroute
|
||||
@@ -1,110 +0,0 @@
|
||||
{
|
||||
"_comment": "Allowlist anti-slopsquatting (check-deps.mjs). Toda dep nova exige adicao EXPLICITA aqui apos verificar que e legitima.",
|
||||
"allowed": [
|
||||
"@aws-sdk/client-bedrock-runtime",
|
||||
"@dnd-kit/core",
|
||||
"@dnd-kit/sortable",
|
||||
"@dnd-kit/utilities",
|
||||
"@huggingface/transformers",
|
||||
"@lobehub/icons",
|
||||
"@modelcontextprotocol/sdk",
|
||||
"@monaco-editor/react",
|
||||
"@ngrok/ngrok",
|
||||
"@playwright/test",
|
||||
"@swc/helpers",
|
||||
"@tailwindcss/postcss",
|
||||
"@testing-library/jest-dom",
|
||||
"@testing-library/react",
|
||||
"@types/bcryptjs",
|
||||
"@types/better-sqlite3",
|
||||
"@types/bun",
|
||||
"@types/keytar",
|
||||
"@types/mdx",
|
||||
"@types/node",
|
||||
"@types/react",
|
||||
"@types/react-dom",
|
||||
"@types/ws",
|
||||
"@vitejs/plugin-react",
|
||||
"@xyflow/react",
|
||||
"axios",
|
||||
"bcryptjs",
|
||||
"better-sqlite3",
|
||||
"bottleneck",
|
||||
"c8",
|
||||
"commander",
|
||||
"concurrently",
|
||||
"cross-env",
|
||||
"csv-stringify",
|
||||
"electron",
|
||||
"electron-builder",
|
||||
"electron-updater",
|
||||
"eslint",
|
||||
"eslint-config-next",
|
||||
"express",
|
||||
"fetch-socks",
|
||||
"fflate",
|
||||
"fumadocs-core",
|
||||
"fumadocs-mdx",
|
||||
"fumadocs-ui",
|
||||
"glob",
|
||||
"gray-matter",
|
||||
"http-proxy-middleware",
|
||||
"https-proxy-agent",
|
||||
"husky",
|
||||
"ink",
|
||||
"ink-spinner",
|
||||
"ink-text-input",
|
||||
"ioredis",
|
||||
"jose",
|
||||
"js-yaml",
|
||||
"jsdom",
|
||||
"jsonc-parser",
|
||||
"keytar",
|
||||
"lint-staged",
|
||||
"lowdb",
|
||||
"lucide-react",
|
||||
"marked",
|
||||
"marked-terminal",
|
||||
"mermaid",
|
||||
"monaco-editor",
|
||||
"next",
|
||||
"next-intl",
|
||||
"next-themes",
|
||||
"node-loader",
|
||||
"node-machine-id",
|
||||
"open",
|
||||
"ora",
|
||||
"parse5",
|
||||
"pino",
|
||||
"pino-abstract-transport",
|
||||
"pino-pretty",
|
||||
"playwright",
|
||||
"prettier",
|
||||
"react",
|
||||
"react-dom",
|
||||
"react-is",
|
||||
"react-markdown",
|
||||
"react-reconciler",
|
||||
"recharts",
|
||||
"selfsigned",
|
||||
"sql.js",
|
||||
"sqlite-vec",
|
||||
"tailwindcss",
|
||||
"tls-client-node",
|
||||
"tsx",
|
||||
"typescript",
|
||||
"typescript-eslint",
|
||||
"undici",
|
||||
"update-notifier",
|
||||
"uuid",
|
||||
"vitest",
|
||||
"wait-on",
|
||||
"wreq-js",
|
||||
"ws",
|
||||
"wtfnode",
|
||||
"xxhash-wasm",
|
||||
"yazl",
|
||||
"zod",
|
||||
"zustand"
|
||||
]
|
||||
}
|
||||
@@ -9,21 +9,6 @@
|
||||
# docker compose -f docker-compose.prod.yml up -d --build
|
||||
# docker compose -f docker-compose.prod.yml down
|
||||
# docker compose -f docker-compose.prod.yml logs -f
|
||||
#
|
||||
# Image flavors (two Dockerfile stages):
|
||||
# runner-base (default / omniroute:prod)
|
||||
# Lean image — no Playwright/Chromium. Suitable for all providers
|
||||
# except web-cookie ones (gemini-web, claude-web, claude-turnstile).
|
||||
#
|
||||
# runner-web (omniroute:prod-web) — opt-in, ~300 MB extra
|
||||
# Includes Playwright + Chromium system libs. Required for web-cookie
|
||||
# providers. To use this flavor, override the build target:
|
||||
#
|
||||
# omniroute-prod:
|
||||
# build:
|
||||
# context: .
|
||||
# target: runner-web
|
||||
# image: omniroute:prod-web
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
services:
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
#
|
||||
# Profiles:
|
||||
# base → minimal image, no CLI tools
|
||||
# web → runner-web (+Chromium/Playwright) for web-cookie providers
|
||||
# cli → CLIs installed inside the container (portable)
|
||||
# host → runner-base + host-mounted CLI binaries (Linux-first)
|
||||
# cliproxyapi → CLIProxyAPI sidecar on port 8317
|
||||
#
|
||||
# Usage:
|
||||
# docker compose --profile base up -d
|
||||
# docker compose --profile web up -d # gemini-web / claude-web / claude-turnstile
|
||||
# docker compose --profile cli up -d
|
||||
# docker compose --profile host up -d
|
||||
# docker compose --profile cliproxyapi up -d
|
||||
@@ -27,7 +25,7 @@ x-common: &common
|
||||
environment:
|
||||
- DATA_DIR=/app/data # Must match the volume mount below
|
||||
- PORT=${PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
@@ -43,7 +41,7 @@ x-common: &common
|
||||
services:
|
||||
# ── Redis (Rate Limiter Backend) ──────────────────────────────────
|
||||
redis:
|
||||
image: docker.io/library/redis:7-alpine
|
||||
image: redis:7-alpine
|
||||
container_name: omniroute-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@@ -66,28 +64,11 @@ services:
|
||||
target: runner-base
|
||||
image: omniroute:base
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
profiles:
|
||||
- base
|
||||
|
||||
# ── Profile: web (runner-web + Chromium/Playwright) ────────────────
|
||||
# Required for web-cookie providers (gemini-web, claude-web, claude-turnstile).
|
||||
# The default `base` image ships without Chromium, so those providers fail
|
||||
# with "Executable doesn't exist at .../ms-playwright/chromium..." (#2832).
|
||||
omniroute-web:
|
||||
<<: *common
|
||||
container_name: omniroute
|
||||
build:
|
||||
context: .
|
||||
target: runner-web
|
||||
image: omniroute:web
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
profiles:
|
||||
- web
|
||||
|
||||
# ── Profile: cli (CLIs installed inside container) ─────────────────
|
||||
omniroute-cli:
|
||||
<<: *common
|
||||
@@ -97,7 +78,7 @@ services:
|
||||
target: runner-cli
|
||||
image: omniroute:cli
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
@@ -116,12 +97,12 @@ services:
|
||||
target: runner-base
|
||||
image: omniroute:base
|
||||
ports:
|
||||
- "${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}"
|
||||
- "${DASHBOARD_PORT:-${PORT:-20128}}:${DASHBOARD_PORT:-${PORT:-20128}}"
|
||||
- "${API_PORT:-20129}:${API_PORT:-20129}"
|
||||
environment:
|
||||
- DATA_DIR=/app/data
|
||||
- PORT=${PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-20128}
|
||||
- DASHBOARD_PORT=${DASHBOARD_PORT:-${PORT:-20128}}
|
||||
- API_PORT=${API_PORT:-20129}
|
||||
- API_HOST=${API_HOST:-0.0.0.0}
|
||||
- CLI_MODE=host
|
||||
|
||||
BIN
docs-overview.png
Normal file
BIN
docs-overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
134
docs/AUTO-COMBO.md
Normal file
134
docs/AUTO-COMBO.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# OmniRoute Auto-Combo Engine
|
||||
|
||||
> Self-managing model chains with adaptive scoring + zero-config auto-routing
|
||||
|
||||
## Zero-Config Auto-Routing (`auto/` prefix)
|
||||
|
||||
> **NEW:** No combo creation required. Use `auto/` prefix directly in any client.
|
||||
|
||||
### Quick Examples
|
||||
|
||||
| Model ID | Variant | Behavior |
|
||||
| -------------- | ------- | ------------------------------------------------------------------------ |
|
||||
| `auto` | default | All connected providers, LKGP strategy, balanced weights |
|
||||
| `auto/coding` | coding | Quality-first weights, suitable for code generation |
|
||||
| `auto/fast` | fast | Low-latency weighted selection |
|
||||
| `auto/cheap` | cheap | Cost-optimized routing (lowest cost first) |
|
||||
| `auto/offline` | offline | Favors providers with highest quota availability |
|
||||
| `auto/smart` | smart | Quality-first + higher exploration rate (10%) for better model discovery |
|
||||
| `auto/lkgp` | lkgp | Explicit LKGP (same as default `auto`) |
|
||||
|
||||
**How to use:**
|
||||
|
||||
```bash
|
||||
# Any IDE or CLI tool that supports OpenAI format
|
||||
Base URL: http://localhost:20128/v1
|
||||
API Key: <your-endpoint-key>
|
||||
|
||||
# In your code/config, set model to:
|
||||
model: "auto" # balanced default
|
||||
model: "auto/coding" # best for coding tasks
|
||||
model: "auto/fast" # fastest available
|
||||
model: "auto/cheap" # cheapest per token
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
|
||||
1. OmniRoute detects `auto/` prefix in `src/sse/handlers/chat.ts`
|
||||
2. Queries all **active provider connections** from the database
|
||||
3. Filters to those with valid credentials (API key or OAuth token)
|
||||
4. Determines the model per connection (`connection.defaultModel` or provider's first model)
|
||||
5. Builds a **virtual combo** in-memory (not stored in DB)
|
||||
6. Routes using the selected variant's weight profile + LKGP strategy
|
||||
|
||||
**Key properties:**
|
||||
|
||||
- ✅ **Always-on:** No toggle, no combo creation, no configuration needed
|
||||
- ✅ **Dynamic:** Reflects current connected providers automatically
|
||||
- ✅ **Session stickiness:** LKGP ensures last successful provider is prioritized
|
||||
- ✅ **Multi-account aware:** Each provider connection becomes a separate candidate
|
||||
- ✅ **No DB writes:** Virtual combo exists only for the request, zero persistence overhead
|
||||
|
||||
**Behind the scenes:**
|
||||
|
||||
```txt
|
||||
Request: { model: "auto/coding" }
|
||||
↓
|
||||
src/sse/handlers/chat.ts detects prefix
|
||||
↓
|
||||
createVirtualAutoCombo('coding') → candidatePool from active connections
|
||||
↓
|
||||
handleComboChat (same engine as persisted combos)
|
||||
↓
|
||||
Auto-scoring selects best provider/model per request
|
||||
```
|
||||
|
||||
**Implementation files:**
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------------------------------------------- | ----------------------------------------- |
|
||||
| `open-sse/services/autoCombo/autoPrefix.ts` | Prefix parser (`parseAutoPrefix`) |
|
||||
| `open-sse/services/autoCombo/virtualFactory.ts` | Creates virtual `AutoComboConfig` objects |
|
||||
| `open-sse/services/autoCombo/providerRegistryAccessor.ts` | Test hook for mocking provider registry |
|
||||
| `src/sse/handlers/chat.ts` | Integration: auto prefix short-circuit |
|
||||
| `src/shared/constants/providers.ts` | `SYSTEM_PROVIDERS.auto` system entry |
|
||||
|
||||
## How It Works (Persisted Auto-Combos)
|
||||
|
||||
The Auto-Combo Engine dynamically selects the best provider/model for each request using a **6-factor scoring function**:
|
||||
|
||||
| Factor | Weight | Description |
|
||||
| :--------- | :----- | :---------------------------------------------- |
|
||||
| Quota | 0.20 | Remaining capacity [0..1] |
|
||||
| Health | 0.25 | Circuit breaker: CLOSED=1.0, HALF=0.5, OPEN=0.0 |
|
||||
| CostInv | 0.20 | Inverse cost (cheaper = higher score) |
|
||||
| LatencyInv | 0.15 | Inverse p95 latency (faster = higher) |
|
||||
| TaskFit | 0.10 | Model × task type fitness score |
|
||||
| Stability | 0.10 | Low variance in latency/errors |
|
||||
|
||||
## Mode Packs
|
||||
|
||||
| Pack | Focus | Key Weight |
|
||||
| :---------------------- | :----------- | :--------------- |
|
||||
| 🚀 **Ship Fast** | Speed | latencyInv: 0.35 |
|
||||
| 💰 **Cost Saver** | Economy | costInv: 0.40 |
|
||||
| 🎯 **Quality First** | Best model | taskFit: 0.40 |
|
||||
| 📡 **Offline Friendly** | Availability | quota: 0.40 |
|
||||
|
||||
## Self-Healing
|
||||
|
||||
- **Temporary exclusion**: Score < 0.2 → excluded for 5 min (progressive backoff, max 30 min)
|
||||
- **Circuit breaker awareness**: OPEN → auto-excluded; HALF_OPEN → probe requests
|
||||
- **Incident mode**: >50% OPEN → disable exploration, maximize stability
|
||||
- **Cooldown recovery**: After exclusion, first request is a "probe" with reduced timeout
|
||||
|
||||
## Bandit Exploration
|
||||
|
||||
5% of requests (configurable) are routed to random providers for exploration. Disabled in incident mode.
|
||||
|
||||
## API
|
||||
|
||||
```bash
|
||||
# Create auto-combo
|
||||
curl -X POST http://localhost:20128/api/combos/auto \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id":"my-auto","name":"Auto Coder","candidatePool":["anthropic","google","openai"],"modePack":"ship-fast"}'
|
||||
|
||||
# List auto-combos
|
||||
curl http://localhost:20128/api/combos/auto
|
||||
```
|
||||
|
||||
## Task Fitness
|
||||
|
||||
30+ models scored across 6 task types (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Supports wildcard patterns (e.g., `*-coder` → high coding score).
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| :------------------------------------------- | :------------------------------------ |
|
||||
| `open-sse/services/autoCombo/scoring.ts` | Scoring function & pool normalization |
|
||||
| `open-sse/services/autoCombo/taskFitness.ts` | Model × task fitness lookup |
|
||||
| `open-sse/services/autoCombo/engine.ts` | Selection logic, bandit, budget cap |
|
||||
| `open-sse/services/autoCombo/selfHealing.ts` | Exclusion, probes, incident mode |
|
||||
| `open-sse/services/autoCombo/modePacks.ts` | 4 weight profiles |
|
||||
| `src/app/api/combos/auto/route.ts` | REST API |
|
||||
@@ -1,334 +0,0 @@
|
||||
# OmniRoute Documentation Overhaul Plan
|
||||
|
||||
> Goal: Make all docs accessible to non-tech users while preserving technical depth for developers.
|
||||
> Strategy: Two-tier docs (User Guide + Technical Reference) with cross-links.
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **User Guide** (new files in `docs/guides/`)
|
||||
- Plain language, no jargon
|
||||
- "Why → What → How" structure
|
||||
- Tables for comparisons
|
||||
- Copy-paste examples
|
||||
- "What's next?" at the end
|
||||
|
||||
2. **Technical Reference** (existing files)
|
||||
- Keep as-is (already well-written)
|
||||
- Add "For Users" callout at top linking to user guide
|
||||
- Add "Prerequisites" section
|
||||
- Ensure consistent formatting
|
||||
|
||||
3. **Cross-Links**
|
||||
- User guides link to technical docs for "learn more"
|
||||
- Technical docs link to user guides for "quick start"
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: User-Facing Quick Start Docs (Highest Impact)
|
||||
|
||||
### 1.1 `docs/guides/QUICK-START.md` — NEW
|
||||
**Replaces**: Condensed version of README.md
|
||||
**Content**:
|
||||
- Install (npm, Docker, source)
|
||||
- Connect a free provider (3 steps)
|
||||
- Point your IDE to OmniRoute
|
||||
- Verify it works
|
||||
- "What's next?" → link to other guides
|
||||
|
||||
### 1.2 `docs/guides/AUTO-COMBO-GUIDE.md` — NEW
|
||||
**Complements**: `docs/routing/AUTO-COMBO.md`
|
||||
**Content**:
|
||||
- What auto-combo does (30-second version)
|
||||
- Which `auto` should I use? (table)
|
||||
- How it picks providers (simple version)
|
||||
- Common questions (FAQ)
|
||||
- "Learn more" → link to technical reference
|
||||
|
||||
### 1.3 `docs/guides/PROVIDERS-GUIDE.md` — NEW
|
||||
**Complements**: `docs/reference/PROVIDER_REFERENCE.md`
|
||||
**Content**:
|
||||
- What is a provider? (analogy)
|
||||
- How to connect a provider (step-by-step)
|
||||
- Free vs paid providers (table)
|
||||
- OAuth vs API key (which do I need?)
|
||||
- Troubleshooting connection issues
|
||||
- "Learn more" → link to provider reference
|
||||
|
||||
### 1.4 `docs/guides/FREE-TIERS-GUIDE.md` — NEW
|
||||
**Complements**: `docs/reference/FREE_TIERS.md`
|
||||
**Content**:
|
||||
- What are free tiers? (analogy)
|
||||
- Best free providers (table with quotas)
|
||||
- How to stack free tiers for unlimited usage
|
||||
- Common questions (FAQ)
|
||||
- "Learn more" → link to free tiers reference
|
||||
|
||||
### 1.5 `docs/guides/TROUBLESHOOTING.md` — REWRITE
|
||||
**Current**: Technical, code-heavy
|
||||
**New**: "I see X → Do Y" format
|
||||
**Content**:
|
||||
- Problem → Solution table
|
||||
- Common error messages (with screenshots)
|
||||
- "Still stuck?" → link to community
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Refine Existing User Docs
|
||||
|
||||
### 2.1 `docs/guides/USER_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "What you'll learn" section at top
|
||||
- Remove jargon, explain terms
|
||||
- Add step-by-step screenshots
|
||||
- Add "Common mistakes" section
|
||||
|
||||
### 2.2 `docs/guides/SETUP_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "Prerequisites" section
|
||||
- Simplify commands (one-liners)
|
||||
- Add "Verify it works" step
|
||||
- Add "Troubleshooting" section
|
||||
|
||||
### 2.3 `docs/guides/FEATURES.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "Why this matters" for each feature
|
||||
- Add use-case examples
|
||||
- Add "How to enable" for each feature
|
||||
- Add screenshots
|
||||
|
||||
### 2.4 `docs/guides/DOCKER_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "Docker for beginners" section
|
||||
- Simplify commands
|
||||
- Add "Common Docker issues" section
|
||||
- Add "What's next?" section
|
||||
|
||||
### 2.5 `docs/guides/ELECTRON_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "What is Electron?" explanation
|
||||
- Add screenshots
|
||||
- Add "Common issues" section
|
||||
|
||||
### 2.6 `docs/guides/TERMUX_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "What is Termux?" explanation
|
||||
- Add step-by-step with screenshots
|
||||
- Add "Common issues" section
|
||||
|
||||
### 2.7 `docs/guides/PWA_GUIDE.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "What is a PWA?" explanation
|
||||
- Add step-by-step with screenshots
|
||||
- Add "Common issues" section
|
||||
|
||||
### 2.8 `docs/guides/I18N.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "How to change language" (user perspective)
|
||||
- Add "How to contribute translations" (contributor perspective)
|
||||
|
||||
### 2.9 `docs/guides/KIRO_SETUP.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "What is Kiro?" explanation
|
||||
- Add step-by-step with screenshots
|
||||
- Add "Common issues" section
|
||||
|
||||
### 2.10 `docs/guides/UNINSTALL.md` — UPDATE
|
||||
**Changes**:
|
||||
- Add "Why uninstall?" section (common reasons)
|
||||
- Add "Before you uninstall" checklist
|
||||
- Simplify commands
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Technical Docs (Add Cross-Links, Keep As-Is)
|
||||
|
||||
### 3.1 `docs/architecture/` (6 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- ARCHITECTURE.md
|
||||
- AUTHZ_GUIDE.md
|
||||
- CODEBASE_DOCUMENTATION.md
|
||||
- MONITORING_SECTIONS.md
|
||||
- REPOSITORY_MAP.md
|
||||
- RESILIENCE_GUIDE.md
|
||||
|
||||
### 3.2 `docs/frameworks/` (16 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- A2A-SERVER.md
|
||||
- AGENT-SKILLS.md
|
||||
- AGENTBRIDGE.md
|
||||
- AGENT_PROTOCOLS_GUIDE.md
|
||||
- CLOUD_AGENT.md
|
||||
- EMBEDDED-SERVICES.md
|
||||
- EVALS.md
|
||||
- GAMIFICATION.md
|
||||
- MCP-SERVER.md
|
||||
- MEMORY.md
|
||||
- OPENCODE.md
|
||||
- PLAYGROUND_STUDIO.md
|
||||
- SEARCH_TOOLS_STUDIO.md
|
||||
- SKILLS.md
|
||||
- TRAFFIC_INSPECTOR.md
|
||||
- WEBHOOKS.md
|
||||
|
||||
### 3.3 `docs/security/` (9 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- CLI_TOKEN.md
|
||||
- CLI_TOKEN_AUTH.md
|
||||
- COMPLIANCE.md
|
||||
- ERROR_SANITIZATION.md
|
||||
- GUARDRAILS.md
|
||||
- PUBLIC_CREDS.md
|
||||
- ROUTE_GUARD_TIERS.md
|
||||
- SOCKET_DEV_FINDINGS.md
|
||||
- STEALTH_GUIDE.md
|
||||
|
||||
### 3.4 `docs/ops/` (8 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- COVERAGE_PLAN.md
|
||||
- E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md
|
||||
- FLY_IO_DEPLOYMENT_GUIDE.md
|
||||
- PROXY_GUIDE.md
|
||||
- RELEASE_CHECKLIST.md
|
||||
- SQLITE_RUNTIME.md
|
||||
- TUNNELS_GUIDE.md
|
||||
- VM_DEPLOYMENT_GUIDE.md
|
||||
|
||||
### 3.5 `docs/compression/` (5 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- COMPRESSION_ENGINES.md
|
||||
- COMPRESSION_GUIDE.md
|
||||
- COMPRESSION_LANGUAGE_PACKS.md
|
||||
- COMPRESSION_RULES_FORMAT.md
|
||||
- RTK_COMPRESSION.md
|
||||
|
||||
### 3.6 `docs/routing/` (3 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- AUTO-COMBO.md (→ link to AUTO-COMBO-GUIDE.md)
|
||||
- QUOTA_SHARE.md
|
||||
- REASONING_REPLAY.md
|
||||
|
||||
### 3.7 `docs/reference/` (5 files)
|
||||
**Changes**: Add "For Users" callout at top → link to user guide
|
||||
- API_REFERENCE.md
|
||||
- CLI-TOOLS.md
|
||||
- ENVIRONMENT.md
|
||||
- FREE_TIERS.md (→ link to FREE-TIERS-GUIDE.md)
|
||||
- PROVIDER_REFERENCE.md (→ link to PROVIDERS-GUIDE.md)
|
||||
|
||||
### 3.8 Other Docs (keep as-is)
|
||||
- `docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md` — Already user-friendly
|
||||
- `docs/marketing/TIERS.md` — Already user-friendly
|
||||
- `docs/diagrams/README.md` — Keep as-is
|
||||
- `docs/dev/plugins.md` — Developer-only
|
||||
- `docs/plugins/PLUGIN_SDK.md` — Developer-only
|
||||
- `docs/providers/ZED-DOCKER.md` — Provider-specific
|
||||
- `docs/AGENTROUTER.md` — Provider-specific
|
||||
- `docs/PROVIDERS.md` — Provider-specific
|
||||
- `docs/README.md` — Keep as-is
|
||||
- `docs/SUBMIT_PR.md` — Contributor-only
|
||||
- `docs/releases/v3.8.0.md` — Release notes
|
||||
- `docs/research/` — Internal research
|
||||
- `docs/specs/` — Internal specs
|
||||
- `docs/openspec/` — Internal specs
|
||||
- `docs/superpowers/` — Internal plans
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Update Main README.md
|
||||
|
||||
**Changes**:
|
||||
- Add "Quick Start" section (3 steps)
|
||||
- Add "Which `auto` should I use?" table
|
||||
- Add "Free providers" table
|
||||
- Add "Common questions" section
|
||||
- Link to user guides
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
|
||||
### Week 1: Phase 1 (User-Facing Quick Start Docs)
|
||||
1. `docs/guides/QUICK-START.md` — NEW
|
||||
2. `docs/guides/AUTO-COMBO-GUIDE.md` — NEW
|
||||
3. `docs/guides/PROVIDERS-GUIDE.md` — NEW
|
||||
4. `docs/guides/FREE-TIERS-GUIDE.md` — NEW
|
||||
5. `docs/guides/TROUBLESHOOTING.md` — REWRITE
|
||||
|
||||
### Week 2: Phase 2 (Refine Existing User Docs)
|
||||
1. `docs/guides/USER_GUIDE.md` — UPDATE
|
||||
2. `docs/guides/SETUP_GUIDE.md` — UPDATE
|
||||
3. `docs/guides/FEATURES.md` — UPDATE
|
||||
4. `docs/guides/DOCKER_GUIDE.md` — UPDATE
|
||||
5. `docs/guides/ELECTRON_GUIDE.md` — UPDATE
|
||||
6. `docs/guides/TERMUX_GUIDE.md` — UPDATE
|
||||
7. `docs/guides/PWA_GUIDE.md` — UPDATE
|
||||
8. `docs/guides/I18N.md` — UPDATE
|
||||
9. `docs/guides/KIRO_SETUP.md` — UPDATE
|
||||
10. `docs/guides/UNINSTALL.md` — UPDATE
|
||||
|
||||
### Week 3: Phase 3 (Technical Docs Cross-Links)
|
||||
1. `docs/architecture/` — Add cross-links
|
||||
2. `docs/frameworks/` — Add cross-links
|
||||
3. `docs/security/` — Add cross-links
|
||||
4. `docs/ops/` — Add cross-links
|
||||
5. `docs/compression/` — Add cross-links
|
||||
6. `docs/routing/` — Add cross-links
|
||||
7. `docs/reference/` — Add cross-links
|
||||
|
||||
### Week 4: Phase 4 (Update Main README.md)
|
||||
1. Update README.md with user-friendly sections
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- [ ] User can install OmniRoute in < 5 minutes (QUICK-START.md)
|
||||
- [ ] User can connect a provider in < 3 minutes (PROVIDERS-GUIDE.md)
|
||||
- [ ] User can use auto-combo in < 1 minute (AUTO-COMBO-GUIDE.md)
|
||||
- [ ] User can find free providers in < 2 minutes (FREE-TIERS-GUIDE.md)
|
||||
- [ ] User can troubleshoot common issues in < 5 minutes (TROUBLESHOOTING.md)
|
||||
- [ ] All technical docs link to user guides
|
||||
- [ ] All user guides link to technical docs
|
||||
|
||||
---
|
||||
|
||||
## File Naming Convention
|
||||
|
||||
- User guides: `docs/guides/[FEATURE]-GUIDE.md` (e.g., `AUTO-COMBO-GUIDE.md`)
|
||||
- Technical docs: Keep existing names (e.g., `docs/routing/AUTO-COMBO.md`)
|
||||
- Cross-links: "For Users" callout at top of technical docs
|
||||
|
||||
---
|
||||
|
||||
## Template for User Guides
|
||||
|
||||
```markdown
|
||||
# [Feature Name]: [One-Line Description]
|
||||
|
||||
## What It Does
|
||||
[2-3 sentences explaining what the feature does in plain language]
|
||||
|
||||
## Quick Start
|
||||
[Step-by-step instructions to get started]
|
||||
|
||||
## [Main Section]
|
||||
[Detailed explanation with tables, examples, screenshots]
|
||||
|
||||
## Common Questions
|
||||
[FAQ section with common questions and answers]
|
||||
|
||||
## What's Next?
|
||||
[Links to related guides and technical docs]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template for Technical Doc Cross-Links
|
||||
|
||||
```markdown
|
||||
> **For Users**: Looking for a quick start? See the [User Guide](../guides/[FEATURE]-GUIDE.md).
|
||||
|
||||
> **Prerequisites**: [List prerequisites]
|
||||
```
|
||||
@@ -22,19 +22,9 @@ How the system is put together — read these to understand the runtime, code la
|
||||
- [AUTHZ_GUIDE.md](architecture/AUTHZ_GUIDE.md) — authorization pipeline (route classifier + policy engine).
|
||||
- [RESILIENCE_GUIDE.md](architecture/RESILIENCE_GUIDE.md) — provider circuit breaker, connection cooldown, and model lockout.
|
||||
|
||||
## For Non-Tech Users
|
||||
## guides/
|
||||
|
||||
Simple guides for using OmniRoute — no technical background needed.
|
||||
|
||||
### getting-started/
|
||||
|
||||
- [QUICK-START.md](getting-started/QUICK-START.md) — install and run OmniRoute in 3 minutes.
|
||||
- [AUTO-COMBO-GUIDE.md](getting-started/AUTO-COMBO-GUIDE.md) — let OmniRoute pick the best AI for you.
|
||||
- [PROVIDERS-GUIDE.md](getting-started/PROVIDERS-GUIDE.md) — how to connect AI providers.
|
||||
- [FREE-TIERS-GUIDE.md](getting-started/FREE-TIERS-GUIDE.md) — get free AI with no credit card.
|
||||
- [TROUBLESHOOTING.md](getting-started/TROUBLESHOOTING.md) — fix common issues.
|
||||
|
||||
### guides/
|
||||
Task-focused walkthroughs for operators and end users.
|
||||
|
||||
- [SETUP_GUIDE.md](guides/SETUP_GUIDE.md) — first-time setup of OmniRoute.
|
||||
- [USER_GUIDE.md](guides/USER_GUIDE.md) — daily usage of the dashboard and API.
|
||||
@@ -42,15 +32,12 @@ Simple guides for using OmniRoute — no technical background needed.
|
||||
- [ELECTRON_GUIDE.md](guides/ELECTRON_GUIDE.md) — desktop (Electron) builds.
|
||||
- [TERMUX_GUIDE.md](guides/TERMUX_GUIDE.md) — running on Android via Termux.
|
||||
- [PWA_GUIDE.md](guides/PWA_GUIDE.md) — installing the dashboard as a PWA.
|
||||
- [TROUBLESHOOTING.md](guides/TROUBLESHOOTING.md) — common issues and fixes.
|
||||
- [UNINSTALL.md](guides/UNINSTALL.md) — clean removal steps.
|
||||
- [I18N.md](guides/I18N.md) — translation and locale workflow.
|
||||
- [FEATURES.md](guides/FEATURES.md) — dashboard feature gallery.
|
||||
|
||||
## For Tech Users
|
||||
|
||||
Technical documentation for developers and contributors.
|
||||
|
||||
## architecture/
|
||||
## reference/
|
||||
|
||||
Lookup material — API surface, environment variables, CLI flags, provider catalog.
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic acr
|
||||
|
||||
Core capabilities:
|
||||
|
||||
- OpenAI-compatible API surface for CLI/tools (177 providers, 55 executors)
|
||||
- OpenAI-compatible API surface for CLI/tools (177 providers, 38 executors)
|
||||
- Request/response translation across provider formats
|
||||
- Model combo fallback (multi-model sequence)
|
||||
- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers`
|
||||
- Account-level fallback (multi-account per provider)
|
||||
- Quota preflight and quota-aware P2C account selection in the main chat path
|
||||
- OAuth + API-key provider connection management (16 OAuth modules)
|
||||
- OAuth + API-key provider connection management (14 OAuth modules)
|
||||
- Embedding generation via `/v1/embeddings` (6 providers, 9 models)
|
||||
- Image generation via `/v1/images/generations` (10+ providers, 20+ models)
|
||||
- Audio transcription via `/v1/audio/transcriptions` (7 providers)
|
||||
@@ -66,7 +66,7 @@ Core capabilities:
|
||||
- Prompt injection guard middleware
|
||||
- Prompt compression pipeline with Caveman, RTK, stacked pipelines, compression combos, language packs, and analytics
|
||||
- ACP (Agent Communication Protocol) registry
|
||||
- Modular OAuth providers (16 individual modules under `src/lib/oauth/providers/`)
|
||||
- Modular OAuth providers (14 individual modules under `src/lib/oauth/providers/`)
|
||||
- Uninstall/full-uninstall scripts
|
||||
- OAuth environment repair action
|
||||
- WebSocket bridge for OpenAI-compatible WS clients (`/v1/ws`)
|
||||
@@ -321,10 +321,10 @@ Domain layer modules:
|
||||
- Eval runner: `src/lib/domain/evalRunner.ts`
|
||||
- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers
|
||||
|
||||
OAuth provider modules (16 individual files under `src/lib/oauth/providers/`):
|
||||
OAuth provider modules (14 individual files under `src/lib/oauth/providers/`):
|
||||
|
||||
- Registry index: `src/lib/oauth/providers/index.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `agy.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`, `windsurf.ts`, `gitlab-duo.ts`, `trae.ts`
|
||||
- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts`, `windsurf.ts`, `gitlab-duo.ts`
|
||||
- Thin wrapper: `src/lib/oauth/providers.ts` — re-exports from individual modules
|
||||
|
||||
## 5) Embedded Services (v3.8.4)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: "Authorization Guide"
|
||||
version: 3.8.2
|
||||
lastUpdated: 2026-06-05
|
||||
lastUpdated: 2026-05-13
|
||||
---
|
||||
|
||||
# Authorization Guide
|
||||
|
||||
> **Source of truth:** `src/server/authz/`, `src/shared/constants/publicApiRoutes.ts`, `src/lib/api/requireManagementAuth.ts`, `src/shared/utils/apiAuth.ts`
|
||||
> **Last updated:** 2026-06-05 — v3.8.2
|
||||
> **Last updated:** 2026-05-13 — v3.8.0
|
||||
|
||||
OmniRoute has a route-aware authorization pipeline that gates every API request. Classification is **deterministic** and **fail-closed** — anything that cannot be classified ends up as `MANAGEMENT` and demands a session or management-grade token. This page explains the model for engineers maintaining routes or designing new endpoints.
|
||||
|
||||
@@ -43,16 +43,16 @@ Some management routes accept **either** mode: cookie OR `Bearer <key>` when the
|
||||
|
||||
`src/server/authz/types.ts` defines three classes; any route that cannot be classified deterministically falls back to `MANAGEMENT`.
|
||||
|
||||
| Class | Description | Auth required |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `PUBLIC` | Explicitly safe routes — login, logout, status, init, health, onboarding bootstrap. | None |
|
||||
| `CLIENT_API` | Model-serving endpoints — `/api/v1/*`, plus aliases `/v1/*`, `/chat/completions`, `/responses`, `/models`, `/codex/*`. | Bearer key when the effective `REQUIRE_API_KEY` feature flag is enabled |
|
||||
| `MANAGEMENT` | Dashboard pages, settings, providers, keys, admin and diagnostics endpoints. | Dashboard session OR Bearer with `manage` scope |
|
||||
| Class | Description | Auth required |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
|
||||
| `PUBLIC` | Explicitly safe routes — login, logout, status, init, health, onboarding bootstrap. | None |
|
||||
| `CLIENT_API` | Model-serving endpoints — `/api/v1/*`, plus aliases `/v1/*`, `/chat/completions`, `/responses`, `/models`, `/codex/*`. | Bearer key (unless `REQUIRE_API_KEY != "true"`) |
|
||||
| `MANAGEMENT` | Dashboard pages, settings, providers, keys, admin and diagnostics endpoints. | Dashboard session OR Bearer with `manage` scope |
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Incoming request → src/proxy.ts
|
||||
Incoming request → src/middleware.ts
|
||||
→ runAuthzPipeline() in src/server/authz/pipeline.ts
|
||||
1. Strip trusted internal headers (x-omniroute-auth-*, x-omniroute-route-class)
|
||||
2. Generate request id, classify route via classifyRoute()
|
||||
@@ -73,7 +73,7 @@ Trusted internal headers (defined in `src/server/authz/headers.ts`) are **stripp
|
||||
Each route class has a policy in `src/server/authz/policies/`:
|
||||
|
||||
- **`publicPolicy`** (`policies/public.ts`) — always returns `allow({ kind: "anonymous", id: "anonymous" })`.
|
||||
- **`clientApiPolicy`** (`policies/clientApi.ts`) — extracts Bearer, validates via `validateApiKey()`. Falls through to anonymous only when the effective `REQUIRE_API_KEY` feature flag is disabled. The effective flag is resolved through `isRequireApiKeyEnabled()` (`DB feature flag override > process.env.REQUIRE_API_KEY > default`) so Dashboard Feature Flags and environment variables govern `/api/v1/*` and aliases consistently; resolver failures fail closed. Allows dashboard-session requests on client API routes (including `/api/v1/models`, used by the dashboard model catalog).
|
||||
- **`clientApiPolicy`** (`policies/clientApi.ts`) — extracts Bearer, validates via `validateApiKey()`. Falls through to anonymous if `REQUIRE_API_KEY != "true"`. Allows dashboard-session GET on `/api/v1/models` (used by the dashboard model catalog).
|
||||
- **`managementPolicy`** (`policies/management.ts`) — accepts dashboard session, internal model-sync requests (matched against `/api/providers/[name]/(sync-models|models)`), or skips entirely if `isAuthRequired()` returns false. Returns 403 (`AUTH_001`) when a Bearer token is present but invalid, 401 otherwise. Also enforces the route-guard tiers (LOCAL_ONLY / ALWAYS_PROTECTED) before any auth branch — see [Route Guard Tiers](../security/ROUTE_GUARD_TIERS.md). LOCAL_ONLY paths in `LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES` (today: `/api/mcp/`) may be accessed from non-loopback when the Bearer key carries the `manage` scope; all other LOCAL_ONLY paths remain strict-loopback regardless of scope.
|
||||
|
||||
A successful policy returns `AuthSubject` with `kind ∈ { client_api_key, dashboard_session, management_key, anonymous }`. Downstream handlers can read it via `assertAuth(request, "CLIENT_API")` in `src/server/authz/assertAuth.ts` instead of re-running auth logic.
|
||||
@@ -173,8 +173,6 @@ Preset bundles (`MCP_SCOPE_PRESETS`): `readonly`, `full`, `monitor`, `agent`. Us
|
||||
- No password configured **and** no `INITIAL_PASSWORD` env var → bootstrap mode allows the onboarding wizard and loopback requests, but exposed network requests still need credentials.
|
||||
- Any DB error → fails closed (secure-by-default).
|
||||
|
||||
Client API key enforcement uses `isRequireApiKeyEnabled()` in `src/shared/utils/featureFlags.ts`, not a direct `process.env.REQUIRE_API_KEY` read. This matters for deployed instances: toggling `REQUIRE_API_KEY` in Dashboard → Feature Flags stores a DB override and immediately affects `/v1/*`, `/models`, `/responses`, `/chat/completions`, `/codex/*`, and other client-API auth checks that share this helper. If the feature flag store cannot be read, client API auth fails closed and requires a key.
|
||||
|
||||
## Breaking Change — v3.8.0
|
||||
|
||||
The `/api/v1/agents/tasks/*` and `/api/resilience/model-cooldowns` endpoints **now require management auth** (commit `588a0333`). Clients previously sending a normal API key without the `manage` scope receive `403`. Migration: either issue the key the `manage` scope in the API Manager dashboard, or use a logged-in dashboard session.
|
||||
|
||||
@@ -274,7 +274,7 @@ table groups the actual directories and notable top-level files.
|
||||
|
||||
| Module | Purpose |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (6 skills: cost analysis, health report, provider discovery, quota management, smart routing, list-capabilities) |
|
||||
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (5 skills: cost analysis, health report, provider discovery, quota management, smart routing) |
|
||||
| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` |
|
||||
| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` |
|
||||
| `auth/` | `managementPassword.ts` (password reset / hashing) |
|
||||
@@ -302,7 +302,6 @@ table groups the actual directories and notable top-level files.
|
||||
| `runtime/` | Runtime feature detection |
|
||||
| `search/` | `executeWebSearch.ts` |
|
||||
| `services/` | Embedded services framework: `ServiceSupervisor.ts` (generic child-process supervisor with operation lock, ring buffer, health checker), `bootstrap.ts` (process-level registration and auto-start), `registry.ts` (tool → supervisor map), `apiKey.ts` (AES-256-GCM key store), `modelSync.ts` (periodic model sync), `ringBuffer.ts` (5 MB circular log buffer), `healthCheck.ts` (HTTP health probe), `types.ts`, `embedWsProxy.ts` (WebSocket proxy), `installers/{ninerouter,cliproxy}.ts`. See `docs/frameworks/EMBEDDED-SERVICES.md` |
|
||||
| `agentSkills/` | Agent Skills catalog + generator: `catalog.ts` (getCatalog/getSkillById/filterCatalog/computeCoverage), `generator.ts` (generateAgentSkills → writes `skills/{id}/SKILL.md`), `openapiParser.ts` (extracts REST endpoints from OpenAPI spec), `cliRegistryParser.ts` (extracts CLI subcommands from bin/cli-registry), `schemas.ts` (Zod: AgentSkillSchema, SkillCoverageSchema, ListQuerySchema, GenerateBodySchema), `types.ts` (AgentSkill, SkillCoverage, SkillMarkdown, GeneratorReport). Consumed by REST routes (`/api/agent-skills/*`), MCP tools (`omniroute_agent_skills_*`), and A2A skill `list-capabilities`. See [AGENT-SKILLS.md](../frameworks/AGENT-SKILLS.md). |
|
||||
| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` |
|
||||
| `spend/` | `batchWriter.ts` (write-behind buffer) |
|
||||
| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) |
|
||||
@@ -452,7 +451,7 @@ open-sse/
|
||||
├── types.d.ts
|
||||
├── config/ Provider registries, header profiles, identity, …
|
||||
├── handlers/ Request handlers (chat, embeddings, audio, image, …)
|
||||
├── executors/ 45 provider-specific HTTP executors
|
||||
├── executors/ 38 provider-specific HTTP executors
|
||||
├── translator/ Format conversion (OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro)
|
||||
├── transformer/ Responses API ↔ Chat Completions stream transformer
|
||||
├── services/ 80+ service modules (combos, fallback, quotas, identity, …)
|
||||
@@ -482,7 +481,7 @@ open-sse/
|
||||
|
||||
### 4.2 `open-sse/executors/`
|
||||
|
||||
45 provider executors, each extending `BaseExecutor` (`base.ts`):
|
||||
38 provider executors, each extending `BaseExecutor` (`base.ts`):
|
||||
|
||||
`antigravity`, `azure-openai`, `blackbox-web`, `chatgpt-web`, `cliproxyapi`,
|
||||
`cloudflare-ai`, `codex`, `commandCode`, `cursor`, `default`, `devin-cli`,
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
---
|
||||
title: "Monitoring & Costs — Navigation Structure"
|
||||
version: 3.8.6
|
||||
lastUpdated: 2026-05-28
|
||||
---
|
||||
|
||||
# Monitoring & Costs — Navigation Structure
|
||||
|
||||
> Implemented in Group B (plan 16). See `src/shared/constants/sidebarVisibility.ts`.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Navigation
|
||||
|
||||
The dashboard sidebar (after Group B) has these top-level sections in order:
|
||||
|
||||
```
|
||||
Home
|
||||
Providers
|
||||
Combos
|
||||
API Keys
|
||||
Settings
|
||||
Analytics
|
||||
Costs ← NEW (Group B, plan 16)
|
||||
Monitoring ← REORGANIZED (Group B, plan 16)
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Costs section (new, level 1)
|
||||
|
||||
Path prefix: `/dashboard/costs/`
|
||||
|
||||
| Item | URL | Description |
|
||||
|------|-----|-------------|
|
||||
| Overview | `/dashboard/costs` | Aggregated cost dashboard (moved from Analytics) |
|
||||
| Pricing | `/dashboard/costs/pricing` | Per-model pricing table |
|
||||
| Budget | `/dashboard/costs/budget` | Budget thresholds + alerts |
|
||||
| Quota Sharing | `/dashboard/costs/quota-share` | Quota Share pools + usage |
|
||||
| Plan Config | `/dashboard/costs/quota-share/plans` | Per-provider plan overrides |
|
||||
|
||||
**Rationale**: Pricing, Budget, and Quota Sharing were previously under
|
||||
`Monitoring > Costs Parameters`. Moving them to a dedicated top-level section
|
||||
makes them discoverable without navigating through observability tooling.
|
||||
|
||||
---
|
||||
|
||||
## Monitoring section (reorganized)
|
||||
|
||||
The Monitoring section now has **Activity at the top** followed by **3 subgroups**:
|
||||
|
||||
```
|
||||
Monitoring
|
||||
├── Activity ← Timeline feed (top-level item)
|
||||
├── Logs group
|
||||
│ ├── Logs (all)
|
||||
│ ├── Proxy Logs
|
||||
│ └── Console Logs
|
||||
├── Audit group
|
||||
│ ├── Audit Log
|
||||
│ ├── MCP Audit
|
||||
│ └── A2A Audit
|
||||
└── System group
|
||||
├── Health
|
||||
└── Runtime
|
||||
```
|
||||
|
||||
### What changed from the old structure
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| Activity = tab inside Logs that rendered the Audit Log | Activity = dedicated feed (`/dashboard/activity`) |
|
||||
| Costs Parameters group in Monitoring | Moved to Costs section |
|
||||
| Flat list: Logs, Activity (logs), Audit, Health, Runtime, Pricing, Budget, Quota | Structured 3-group + dedicated Costs section |
|
||||
|
||||
---
|
||||
|
||||
## Activity vs Audit Log
|
||||
|
||||
These two are now distinct:
|
||||
|
||||
| Dimension | Activity (`/dashboard/activity`) | Audit Log (`/dashboard/audit`) |
|
||||
|-----------|----------------------------------|-------------------------------|
|
||||
| **Purpose** | User-facing event feed ("what happened recently") | Compliance / security log |
|
||||
| **Data source** | `GET /api/compliance/audit-log?level=high` | `GET /api/compliance/audit-log?level=all` |
|
||||
| **Format** | Timeline, grouped by day, human-readable verbs + icons | Dense paginaged table, 50/page |
|
||||
| **Filters** | Event type category | Action, severity, actor, date range |
|
||||
| **Export** | Not available | JSON export |
|
||||
| **Actor filter** | Not applicable | Filterable by actor |
|
||||
| **Events shown** | High-level actions only (allowlist) | All audit events |
|
||||
|
||||
### High-Level Actions allowlist
|
||||
|
||||
Defined in `src/lib/audit/highLevelActions.ts`. Controls which events appear in
|
||||
the Activity feed. The allowlist includes:
|
||||
|
||||
- Provider add/remove/test events
|
||||
- Combo create/update/delete
|
||||
- API key lifecycle (create, revoke, rotate)
|
||||
- Budget threshold reached
|
||||
- Auth login/logout
|
||||
- Cloud agent session creation
|
||||
- MCP tool registration
|
||||
- Webhook create/delete
|
||||
- Quota pool/plan changes (`quota.*` actions, Group B)
|
||||
- Platform events (update, deploy)
|
||||
- Skill install/remove
|
||||
|
||||
Events not in this list appear only in the Audit Log.
|
||||
|
||||
### Adding a new high-level action
|
||||
|
||||
Edit `src/lib/audit/highLevelActions.ts` and add the action string to
|
||||
`HIGH_LEVEL_ACTIONS`. This requires a PR (the list is code, not DB-configurable).
|
||||
The corresponding icon can be added to `src/lib/audit/activityIcons.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Redirect: `/dashboard/logs/activity`
|
||||
|
||||
The old path `/dashboard/logs/activity` is permanently redirected (HTTP 308) to
|
||||
`/dashboard/activity` via `permanentRedirect()` in
|
||||
`src/app/(dashboard)/dashboard/logs/activity/page.tsx`.
|
||||
|
||||
The legacy sidebar ID `logs-activity` is preserved in `HIDEABLE_SIDEBAR_ITEM_IDS`
|
||||
(but removed from `SIDEBAR_DEFINITIONS`) to avoid breaking user presets that
|
||||
reference the old ID.
|
||||
|
||||
---
|
||||
|
||||
## i18n
|
||||
|
||||
Namespaces added by Group B:
|
||||
|
||||
| Namespace key | Covers |
|
||||
|---------------|--------|
|
||||
| `sidebar.costsSection` | Costs section label |
|
||||
| `sidebar.activity` | Activity sidebar item |
|
||||
| `sidebar.logsGroup` | Logs subgroup label |
|
||||
| `sidebar.systemGroup` | System subgroup label |
|
||||
| `sidebar.costsOverview` | Costs overview item |
|
||||
| `activity.*` | All Activity page strings (title, verbs, filters, empty state) |
|
||||
|
||||
Source-of-truth locales: `pt-BR` and `en`. All other 39 locales fall back to
|
||||
English via the fallback mechanism (`src/i18n/fallback.ts` or `next-intl` fallback).
|
||||
@@ -101,9 +101,7 @@ src/
|
||||
├── shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils)
|
||||
├── i18n/ # next-intl config + per-locale message JSON (30+ locales)
|
||||
├── middleware/ # Next.js middleware (request enrichment, locale detection)
|
||||
├── mitm/ # MITM proxy core: cert gen/install, handlers, targets, inspector, masks, passthrough
|
||||
│ ├── handlers/ # 9 IDE-agent handler classes extending MitmHandlerBase (antigravity, kiro, copilot, codex, cursor, zed, claudeCode, openCode, trae)
|
||||
│ └── inspector/ # Traffic capture layer: buffer (in-memory ring), sseMerger, conversationNormalizer, kindDetector, contextKey, httpProxyServer, systemProxyConfig
|
||||
├── mitm/ # MITM proxy helpers (Linux cert install, antigravity stealth)
|
||||
├── models/ # Model adapter glue (legacy shim)
|
||||
├── scripts/ # In-tree maintenance scripts (e.g., backfillAggregation)
|
||||
├── sse/ # Legacy SSE handlers/services (chat.ts, chatHelpers.ts, services/auth.ts)
|
||||
@@ -121,21 +119,10 @@ src/
|
||||
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `app/api/v1/` | Public OpenAI-compat API (~25 sub-routes: chat, completions, embeddings, files, batches, audio, images, videos, music, rerank, moderations, search, ws, agents, accounts, providers, etc.) |
|
||||
| `app/api/v1beta/` | Gemini-style API endpoints |
|
||||
| `app/api/playground/` | Playground Studio routes: `improve-prompt/` (POST — LLM prompt rewriter), `presets/` (GET list / POST create), `presets/[id]/` (GET / PUT / DELETE) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
|
||||
| `app/api/` (non-v1) | Management/admin routes (~60 directories: providers, combos, settings, mcp, a2a, evals, memory, skills, webhooks, compliance, resilience, monitoring, tunnels, cli-tools, etc.) |
|
||||
| `app/api/tools/agent-bridge/` | AgentBridge REST API — 12 routes (server control, agent state/DNS/mappings, bypass, cert, upstream-CA). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/AGENTBRIDGE.md §7`. |
|
||||
| `app/api/tools/traffic-inspector/` | Traffic Inspector REST + WS API — 16+ routes (requests, sessions, hosts, capture-modes, export, ws). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/TRAFFIC_INSPECTOR.md §8`. |
|
||||
| `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) |
|
||||
| `app/.well-known/agent.json/` | A2A Agent Card (discovery) |
|
||||
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~35 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, activity, etc.) |
|
||||
| `app/(dashboard)/dashboard/search-tools/` | Search Tools Studio UI (3 tabs: Search/Scrape/Compare + SearchConceptCard + ProviderCatalog) — see `docs/frameworks/SEARCH_TOOLS_STUDIO.md` |
|
||||
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~30 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, etc.) |
|
||||
| `app/(dashboard)/dashboard/memory/` | Memory Studio (plan 21): `page.tsx` (3-tab shell), `components/` (MemoryConceptCard, MemoryEngineStatus, EmbeddingSourceSelector, EditMemoryModal, RetrievePreview, QdrantConfigCard, RerankConfigCard), `components/tabs/` (MemoriesTab, PlaygroundTab, EngineTab), `hooks/` (useEngineStatus, useMemorySettings) |
|
||||
| `app/(dashboard)/dashboard/tools/agent-bridge/` | AgentBridge dashboard page — server card, 9 agent cards, setup wizard, model mapping, bypass list. i18n PT-BR + EN. See `docs/frameworks/AGENTBRIDGE.md`. |
|
||||
| `app/(dashboard)/dashboard/tools/traffic-inspector/` | Traffic Inspector dashboard page — DevTools split, 7 detail tabs, 4 capture mode toggles, session recorder, context colorization. i18n PT-BR + EN. See `docs/frameworks/TRAFFIC_INSPECTOR.md`. |
|
||||
| `app/(dashboard)/dashboard/activity/` | Activity feed page (Group B): `page.tsx` (server) + `ActivityFeedClient.tsx` + `components/{ActivityFeed,ActivityItem,DayHeader,EventTypeFilter}.tsx` — see `docs/architecture/MONITORING_SECTIONS.md` |
|
||||
| `app/(dashboard)/dashboard/costs/quota-share/` | Quota Sharing page (Group B): `QuotaSharePageClient.tsx` + `components/{PoolCard,DimensionBar,AllocationTable,BurnRateChart,QuotaConceptCard,CreatePoolModal,EditAllocationsModal}.tsx` + `hooks/{usePools,usePoolUsage,useLocalStoragePoolMigration}.ts` |
|
||||
| `app/(dashboard)/dashboard/costs/quota-share/plans/` | Provider plan config page (Group B): `page.tsx` + `ProviderPlanConfigClient.tsx` — quota dimensions per connection override |
|
||||
| `app/docs/` | Embedded documentation viewer (renders `docs/*.md`) |
|
||||
| `app/landing/` | Marketing landing page |
|
||||
| `app/login/`, `forgot-password/`, `forbidden/` | Auth-related pages |
|
||||
@@ -156,28 +143,22 @@ src/
|
||||
| `catalog/` | Provider catalog Zod validation + capability resolution |
|
||||
| `cloudAgent/` | Cloud Agents (Codex Cloud, Devin, Jules) — see `docs/frameworks/CLOUD_AGENT.md` |
|
||||
| `combos/` | Combo resolution + reorder helpers |
|
||||
| `audit/` | Activity feed helpers: `highLevelActions.ts` (allowlist + `isHighLevelAction()`), `activityIcons.ts` (action → icon/verb map), `timeline.ts` (groupByDay/relativeTime) — see `docs/architecture/MONITORING_SECTIONS.md` |
|
||||
| `compliance/` | Audit log + provider audit — see `docs/security/COMPLIANCE.md` |
|
||||
| `compression/` | Compression engine glue (engines live in `open-sse/services/compression/`) |
|
||||
| `config/` | Runtime config helpers |
|
||||
| `db/` | 45+ domain DB modules + 55 migrations (always go through here for SQLite) |
|
||||
| `quota/` | Quota Sharing Engine: `dimensions.ts` (types/Zod), `types.ts` (QuotaStore interface), `sqliteQuotaStore.ts`, `redisQuotaStore.ts`, `storeFactory.ts`, `fairShare.ts`, `burnRate.ts`, `planResolver.ts`, `planRegistry.ts`, `saturationSignals.ts`, `enforce.ts`, `spendRecorder.ts` — see `docs/routing/QUOTA_SHARE.md` |
|
||||
| `display/` | UI formatting helpers (cost, latency, etc.) |
|
||||
| `embeddings/` | Embeddings service helpers |
|
||||
| `env/` | Env variable parsing + validation |
|
||||
| `evals/` | Eval framework (suites, runner, runtime) — see `docs/frameworks/EVALS.md` |
|
||||
| `guardrails/` | PII masker, prompt injection, vision bridge — see `docs/security/GUARDRAILS.md` |
|
||||
| `jobs/` | Background jobs (cron-like) |
|
||||
| `memory/` | Conversational memory (SQLite FTS5 + sqlite-vec hybrid RRF + Qdrant tier 2) — see `docs/frameworks/MEMORY.md` |
|
||||
| `memory/embedding/` | Multi-source embedding layer: `index.ts` (resolver), `remote.ts`, `staticPotion.ts`, `transformersLocal.ts`, `cache.ts`, `types.ts` (plan 21) |
|
||||
| `memory/vectorStore.ts` | sqlite-vec v0.1.9 wrapper — KNN brute-force + hybrid RRF (FTS5 + vector, k=60). Lazy-init, degrades gracefully when sqlite-vec unavailable. (plan 21) |
|
||||
| `memory/reindex.ts` | `runReindexBatch()` — processes memories with `needs_reindex=1` in background; called by `POST /api/memory/reindex` and lazy-backfill path. (plan 21) |
|
||||
| `memory/` | Conversational memory (SQLite FTS5 + Qdrant) — see `docs/frameworks/MEMORY.md` |
|
||||
| `monitoring/` | Health checks, metrics emission |
|
||||
| `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) |
|
||||
| `plugins/` | Plugin registry |
|
||||
| `promptCache/` | Anthropic-style prompt cache breakpoints |
|
||||
| `skills/` | Skills framework (built-in + marketplace + SkillsSH) — see `docs/frameworks/SKILLS.md` |
|
||||
| `playground/` | Playground Studio shared helpers: `codeExport.ts` (curl/Python/TS generator), `promptImprover.ts` (meta-prompt builder), `streamMetrics.ts` (pure TTFT/TPS), `types.ts` (pricing table) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
|
||||
| `webhookDispatcher.ts` | HMAC webhook delivery — see `docs/frameworks/WEBHOOKS.md` |
|
||||
| `cloudflaredTunnel.ts`, `ngrokTunnel.ts` | Tunnel managers — see `docs/ops/TUNNELS_GUIDE.md` |
|
||||
| `oneproxySync.ts`, `oneproxyRotator.ts` | 1proxy free proxy marketplace — see `docs/ops/PROXY_GUIDE.md` |
|
||||
@@ -191,9 +172,7 @@ src/
|
||||
| Subdir | Purpose |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `db/core.ts` | `getDbInstance()` singleton with WAL journaling |
|
||||
| `db/migrations/` | Versioned SQL files (idempotent, transactional). `073_memory_vec.sql` adds `memory_vec_meta` + `needs_reindex` column (plan 21). |
|
||||
| `db/playgroundPresets.ts` | CRUD module for Playground Studio presets (`listPlaygroundPresets`, `getPlaygroundPreset`, `createPlaygroundPreset`, `updatePlaygroundPreset`, `deletePlaygroundPreset`) |
|
||||
| `db/memoryVec.ts`| CRUD for `memory_vec_meta` (active_dim, embedding_signature, last_reset_at, vec_loaded) + `markMemoryNeedsReindex`, `getMemoryReindexQueue`, etc. (plan 21) |
|
||||
| `db/migrations/` | 55 versioned SQL files (idempotent, transactional, numbered `001`..`055`) |
|
||||
| `db/<domain>.ts` | One module per domain: providers, combos, apiKeys, users, sessions, usage, audit*log, webhooks, skills, memory_entries, cloud_agent_tasks, evals*\*, reasoning_cache, etc. |
|
||||
|
||||
### `src/domain/`
|
||||
@@ -491,7 +470,7 @@ Shipped configuration templates and sample files (referenced by setup wizard).
|
||||
| `commands/deploy-vps-{local,akamai,both}-cc.md` | Deploy to VPS |
|
||||
| `commands/capture-release-evidences-cc.md` | Browser-record new features as WebP |
|
||||
| `commands/review-{prs,discussions}-cc.md` | Triage GitHub PRs/discussions |
|
||||
| `commands/{review-issues,implement-features}-cc.md` | Issue workflows |
|
||||
| `commands/{issue-triage,resolve-issues,implement-features}-cc.md` | Issue workflows |
|
||||
| `settings.local.json` | Per-project Claude Code settings |
|
||||
|
||||
---
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
Feature: Self-service API key usage and account quota visibility
|
||||
|
||||
Background:
|
||||
Given OmniRoute has usage accounting enabled
|
||||
And management APIs require a dashboard session or a key with "manage" or "admin"
|
||||
|
||||
Scenario: A delegated key reads its own cost and token usage
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" already has a monthly USD budget of 50 configured in the existing budget UI
|
||||
And "team-a" has current-period spend of 12.50 USD
|
||||
And "team-a" has current-period token usage:
|
||||
| input | output | cache_read | cache_creation | reasoning |
|
||||
| 900000 | 32000 | 120000 | 10000 | 5000 |
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response apiKey.name should be "team-a"
|
||||
And the response usage.cost.limitUsd should be 50
|
||||
And the response usage.cost.usedUsd should be 12.50
|
||||
And the response usage.cost.usedPercent should be 25
|
||||
And the response usage.tokens.totalTokens should be 1067000
|
||||
|
||||
Scenario: A delegated key cannot query another key by id
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And an API key named "team-b" has the scope "self:usage"
|
||||
And "team-b" has current-period spend of 99.00 USD
|
||||
When "team-a" calls GET "/api/v1/me/status?apiKeyId=<team-b-id>" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response apiKey.name should be "team-a"
|
||||
And the response should not contain "team-b"
|
||||
And the response should not contain "99.00" as team-b usage
|
||||
|
||||
Scenario: Anonymous client API mode does not expose self-service status
|
||||
Given global client API auth allows anonymous local traffic
|
||||
When an anonymous caller calls GET "/api/v1/me/status"
|
||||
Then the response status should be 401
|
||||
|
||||
Scenario: Self-service usage scope does not grant management access
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" does not have the scope "manage"
|
||||
And "team-a" does not have the scope "admin"
|
||||
When "team-a" calls GET "/api/usage/history" with its Bearer token
|
||||
Then the response status should be 403
|
||||
|
||||
Scenario: Own usage visibility can be disabled
|
||||
Given an API key named "team-a" does not have the scope "self:usage"
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 403
|
||||
|
||||
Scenario: Existing ordinary keys are backfilled for own usage visibility
|
||||
Given an ordinary API key named "legacy-key" existed before self-service usage scopes
|
||||
And "legacy-key" does not have the scope "self:usage"
|
||||
When OmniRoute runs the compatibility migration
|
||||
Then "legacy-key" should have the scope "self:usage"
|
||||
And "legacy-key" should not have the scope "self:account-quota"
|
||||
|
||||
Scenario: Shared account quota is hidden by default
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" does not have the scope "self:account-quota"
|
||||
And "team-a" is restricted to a Codex connection with available quota
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response should not include shared account quota details
|
||||
|
||||
Scenario: Shared provider account quotas are visible with explicit permission
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" has the scope "self:account-quota"
|
||||
And "team-a" is restricted to a Codex connection and a Claude connection
|
||||
And Codex reports a session quota with 1 percent used
|
||||
And Claude reports a daily quota with 35 percent used
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response accountQuotas should contain 2 entries
|
||||
And the first response accountQuotas entry provider should be "codex"
|
||||
And the first response accountQuotas entry quotas.session.remainingPercentage should be 99
|
||||
And the second response accountQuotas entry provider should be "claude"
|
||||
And the second response accountQuotas entry quotas.daily.remainingPercentage should be 65
|
||||
|
||||
Scenario: A single allowed provider also keeps the compatibility accountQuota field
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" has the scope "self:account-quota"
|
||||
And "team-a" is restricted to exactly one Codex connection
|
||||
And Codex reports a weekly quota with 97 percent used
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response accountQuotas should contain 1 entry
|
||||
And the response accountQuota.provider should be "codex"
|
||||
And the response accountQuota.quotas.weekly.remainingPercentage should be 3
|
||||
|
||||
Scenario: Unrestricted keys can see all active provider account quotas
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" has the scope "self:account-quota"
|
||||
And "team-a" has no explicit allowed connection restrictions
|
||||
And OmniRoute has active Codex and Cursor provider connections with quota data
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response accountQuotas should contain the Codex account quota
|
||||
And the response accountQuotas should contain the Cursor account quota
|
||||
|
||||
Scenario: Provider connection lookup failures do not hide own usage
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" has the scope "self:account-quota"
|
||||
And "team-a" is restricted to a Codex connection and another provider connection
|
||||
And OmniRoute cannot resolve the other provider connection metadata
|
||||
When "team-a" calls GET "/api/v1/me/status" with its Bearer token
|
||||
Then the response status should be 200
|
||||
And the response should still include own cost and token usage
|
||||
And the unresolved response accountQuotas entry should have available false
|
||||
And the unresolved response accountQuotas entry reason should be "connection_lookup_failed"
|
||||
|
||||
Scenario: Existing budget endpoint stays management-only
|
||||
Given an API key named "team-a" has the scope "self:usage"
|
||||
And "team-a" does not have the scope "manage"
|
||||
When "team-a" calls GET "/api/usage/budget?apiKeyId=<another-key-id>" with its Bearer token
|
||||
Then the response status should be 403
|
||||
|
||||
Scenario: API Manager defaults are privacy-preserving
|
||||
Given an operator opens the create API key dialog
|
||||
Then the own cost and token usage visibility control should be checked
|
||||
And the shared account quota visibility control should be unchecked
|
||||
And management access should be unchecked
|
||||
And the dialog should not include a second budget editor
|
||||
|
||||
Scenario: API Manager preserves unrelated scopes
|
||||
Given an API key has scopes:
|
||||
| scope |
|
||||
| self:usage |
|
||||
| custom:scope |
|
||||
When an operator enables shared account quota in the permissions dialog
|
||||
And saves the permissions
|
||||
Then the API key scopes should include "self:usage"
|
||||
And the API key scopes should include "self:account-quota"
|
||||
And the API key scopes should include "custom:scope"
|
||||
|
||||
Scenario: API Manager uses existing budget configuration
|
||||
Given an operator wants to set a monthly USD budget for an API key
|
||||
When the operator uses the dashboard
|
||||
Then the operator should use the existing budget configuration surface
|
||||
And the create key dialog should not save budget limits
|
||||
|
||||
Scenario: New API Manager text is localized
|
||||
Given the dashboard locale is not English
|
||||
When the API Manager renders self-service visibility controls
|
||||
Then the labels should come from the API Manager translation namespace
|
||||
And the component should not render hard-coded English strings for the new controls
|
||||
@@ -271,206 +271,6 @@ RTK mode is inspired by **[RTK - Rust Token Killer](https://github.com/rtk-ai/rt
|
||||
|
||||
---
|
||||
|
||||
## Advanced Compression Systems
|
||||
|
||||
Beyond the 7 standard modes, OmniRoute includes several advanced compression
|
||||
systems that work automatically based on context.
|
||||
|
||||
### Cache-Aware Compression
|
||||
|
||||
Some providers (like Anthropic with prompt caching) support **prompt caching**,
|
||||
which lets them cache parts of the prompt to reduce costs and latency. When
|
||||
caching is enabled, aggressive compression can actually **hurt** performance
|
||||
because it changes the cached tokens, invalidating the cache.
|
||||
|
||||
The `cachingAware.ts` module solves this by **detecting caching context** and
|
||||
**adjusting the compression strategy** accordingly.
|
||||
|
||||
#### How it works
|
||||
|
||||
1. **Detect caching context** — Scans the request body for `cache_control` markers
|
||||
2. **Identify caching providers** — Checks if the target provider supports caching
|
||||
3. **Adjust strategy** — Downgrades `aggressive`/`ultra` to `standard` for caching providers
|
||||
4. **Skip system prompt** — System prompts are usually cached, so don't compress them
|
||||
5. **Use deterministic transformations** — Only use transformations that produce consistent output
|
||||
|
||||
#### Code example
|
||||
|
||||
```ts
|
||||
import { detectCachingContext, getCacheAwareStrategy } from "@omniroute/open-sse/services/compression/cachingAware";
|
||||
|
||||
const body = {
|
||||
model: "anthropic/claude-sonnet-4.5",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
cache_control: { type: "ephemeral" }, // ← Cache marker
|
||||
};
|
||||
|
||||
const ctx = detectCachingContext(body, { provider: "anthropic" });
|
||||
// → { hasCacheControl: true, provider: "anthropic", isCachingProvider: true }
|
||||
|
||||
const strategy = getCacheAwareStrategy("aggressive", ctx);
|
||||
// → { strategy: "standard", skipSystemPrompt: true, deterministicOnly: true }
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
Cache-aware compression is **always on** — no configuration needed. It only kicks in
|
||||
when:
|
||||
- The request has `cache_control` markers
|
||||
- The target provider supports prompt caching (Anthropic, OpenAI, etc.)
|
||||
|
||||
### Progressive Aging
|
||||
|
||||
Long conversations accumulate many message turns, but older turns become less
|
||||
relevant. The `progressiveAging.ts` module **degrades messages by turn distance**:
|
||||
|
||||
- **Recent turns (0-3)**: Kept verbatim (full detail)
|
||||
- **Medium turns (4-8)**: Lite compression (whitespace, formatting cleanup)
|
||||
- **Old turns (9+)**: Caveman compression (filler removal, summarization)
|
||||
- **Very old turns (20+)**: Heavily summarized or dropped
|
||||
|
||||
#### Code example
|
||||
|
||||
```ts
|
||||
import { applyAging } from "@omniroute/open-sse/services/compression/progressiveAging";
|
||||
|
||||
const messages = [
|
||||
{ role: "system", content: "You are a helpful assistant" },
|
||||
{ role: "user", content: "What is 2+2?" },
|
||||
{ role: "assistant", content: "4" },
|
||||
// ... 50 more turns ...
|
||||
];
|
||||
|
||||
const { messages: aged, saved } = applyAging(messages, {
|
||||
verbatim: 3, // First 3 turns: verbatim
|
||||
light: 8, // Turns 4-8: lite compression
|
||||
moderate: 20, // Turns 9-20: caveman compression
|
||||
// Turns 21+: heavy summarization
|
||||
});
|
||||
|
||||
// saved = number of tokens saved
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
Progressive aging is **always on** for `aggressive` and `ultra` modes. It's
|
||||
particularly effective for:
|
||||
- Long-running coding sessions
|
||||
- Multi-day conversations
|
||||
- Agentic workflows with many tool calls
|
||||
|
||||
### Caveman Output Mode
|
||||
|
||||
The `outputMode.ts` module injects **system prompt instructions** to make the
|
||||
model itself produce compressed, terse output (a "caveman" style).
|
||||
|
||||
#### How it works
|
||||
|
||||
Instead of compressing the input, this mode adds a system prompt like:
|
||||
> "Reply in minimal words. Skip pleasantries. Use short sentences."
|
||||
|
||||
This works particularly well for:
|
||||
- Code generation (terser output = fewer tokens)
|
||||
- Quick Q&A (no need for elaborate explanations)
|
||||
- Batch processing (maximize throughput)
|
||||
|
||||
#### When to use
|
||||
|
||||
Caveman output mode is **opt-in** — set it via the combo config:
|
||||
```json
|
||||
{
|
||||
"strategy": "auto",
|
||||
"config": {
|
||||
"auto": {
|
||||
"outputMode": "caveman"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Result Compression
|
||||
|
||||
The `toolResultCompressor.ts` module provides **5 specialized compression strategies**
|
||||
for tool results (function calls, agent outputs, search results, etc.):
|
||||
|
||||
1. **Search result compression** — Removes redundant results, keeps top-N
|
||||
2. **File read compression** — Truncates large files, preserves headers/imports
|
||||
3. **Code execution compression** — Keeps only essential stdout/stderr
|
||||
4. **Database query compression** — Limits rows, removes verbose metadata
|
||||
5. **API response compression** — Strips null fields, condenses arrays
|
||||
|
||||
#### When to use
|
||||
|
||||
Tool result compression is **always on** when tool calls are present. No
|
||||
configuration needed.
|
||||
|
||||
### Stacked Pipeline
|
||||
|
||||
The stacked mode runs **multiple engines in sequence** — usually RTK first
|
||||
(60-90% savings on tool output), then Caveman (30% additional savings on the
|
||||
remaining text). This achieves **78-95% total savings**.
|
||||
|
||||
#### How it works
|
||||
|
||||
```
|
||||
Input (1000 tokens)
|
||||
→ RTK (command-aware filter) → 200 tokens
|
||||
→ Caveman (filler removal) → 140 tokens
|
||||
→ Output (140 tokens, 86% savings)
|
||||
```
|
||||
|
||||
#### When to use
|
||||
|
||||
Use stacked mode for:
|
||||
- Tool-heavy workflows (agentic coding, research)
|
||||
- Cost-sensitive batch processing
|
||||
- When you need maximum token savings
|
||||
|
||||
Configure via combo:
|
||||
```json
|
||||
{
|
||||
"strategy": "auto",
|
||||
"config": {
|
||||
"auto": {
|
||||
"modePack": "stacked"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compression Combo Overrides
|
||||
|
||||
You can override the global compression mode **per combo** to fine-tune behavior
|
||||
for different use cases:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "coding-combo",
|
||||
"strategy": "priority",
|
||||
"config": {
|
||||
"auto": {
|
||||
"weights": { "taskFit": 0.5 },
|
||||
"modePack": "quality-first"
|
||||
}
|
||||
},
|
||||
"compressionOverride": {
|
||||
"mode": "aggressive",
|
||||
"stackedPipelines": ["rtk", "caveman"],
|
||||
"preserveToolDefinitions": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
- **Coding combos**: Use `aggressive` mode for long sessions
|
||||
- **Quick Q&A combos**: Use `lite` mode for fast responses
|
||||
- **Tool-heavy combos**: Use `stacked` mode for max savings
|
||||
- **Production combos**: Use `cache-aware` mode for caching providers
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [Environment Config](../reference/ENVIRONMENT.md) — Compression environment variables
|
||||
|
||||
@@ -24,14 +24,12 @@ Current shipped packs (verified against `rules/` directory contents):
|
||||
| ------------------- | -------------- | --------------------------------------------------- |
|
||||
| English | `rules/en/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
|
||||
| Spanish | `rules/es/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
|
||||
| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
|
||||
| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `filler`, `structural` |
|
||||
| German | `rules/de/` | `context`, `filler`, `structural` |
|
||||
| French | `rules/fr/` | `context`, `filler`, `structural` |
|
||||
| Japanese | `rules/ja/` | `context`, `filler`, `structural` |
|
||||
|
||||
> **Parity note:** `en`, `es`, and `pt-BR` packs have the full 5 categories; `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs.
|
||||
>
|
||||
> The `pt-BR` pack is based on **[Troglodita](https://github.com/leninejunior/troglodita)** by Lenine Júnior — a compression system designed from scratch for Brazilian Portuguese grammar (pleonasm reduction, PT-BR filler removal, technical abbreviations for the dev BR community).
|
||||
> **Parity note:** `en` and `es` packs have the full 5 categories; `pt-BR`, `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs.
|
||||
>
|
||||
> The canonical category list and per-category schema live in [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12).
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Mermaid sources (`.mmd`) and exported SVGs for OmniRoute v3.8.0 architecture flo
|
||||
| [auto-combo-9factor.mmd](./auto-combo-9factor.mmd) | [SVG](./exported/auto-combo-9factor.svg) | docs/routing/AUTO-COMBO.md |
|
||||
| [resilience-3layers.mmd](./resilience-3layers.mmd) | [SVG](./exported/resilience-3layers.svg) | docs/architecture/RESILIENCE_GUIDE.md, CLAUDE.md |
|
||||
| [i18n-flow.mmd](./i18n-flow.mmd) | [SVG](./exported/i18n-flow.svg) | docs/guides/I18N.md |
|
||||
| [mcp-tools-43.mmd](./mcp-tools-43.mmd) | [SVG](./exported/mcp-tools-43.svg) | docs/frameworks/MCP-SERVER.md |
|
||||
| [mcp-tools-37.mmd](./mcp-tools-37.mmd) | [SVG](./exported/mcp-tools-37.svg) | docs/frameworks/MCP-SERVER.md |
|
||||
| [cloud-agent-flow.mmd](./cloud-agent-flow.mmd) | [SVG](./exported/cloud-agent-flow.svg) | docs/frameworks/CLOUD_AGENT.md |
|
||||
| [authz-pipeline.mmd](./authz-pipeline.mmd) | [SVG](./exported/authz-pipeline.svg) | docs/architecture/AUTHZ_GUIDE.md |
|
||||
| [db-schema-overview.mmd](./db-schema-overview.mmd) | [SVG](./exported/db-schema-overview.svg) | docs/architecture/CODEBASE_DOCUMENTATION.md |
|
||||
|
||||
1
docs/diagrams/exported/mcp-tools-37.svg
Normal file
1
docs/diagrams/exported/mcp-tools-37.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 42 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user