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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ MODEL_IDLE_TIMEOUT_SEC=180
# Runtime mode for optional Rust-backed provider/kernel paths.
# off — default; use Python implementations.
# required — selected Rust-backed paths must run and hard-fail on import/call errors.
# Currently selected paths: voiceprint scoring and result post-processing.
# Currently selected paths: voiceprint scoring, result post-processing, and
# artifact manifest helper contracts.
# CI/Docker packaging still validates the Rust extension directly even when
# the runtime default is off.
RUST_KERNEL_MODE=off
Expand Down
69 changes: 8 additions & 61 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Claude Code Review

on:
pull_request:
types: [opened, reopened, ready_for_review]
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]

permissions:
Expand All @@ -21,26 +21,17 @@ jobs:
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)
}}
env:
HAS_ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }}
HAS_ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL != '' }}
HAS_CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
CLAUDE_MODEL: claude-sonnet-4-6
steps:
- name: Skip when Claude secrets are not configured
if: >-
${{
env.HAS_CLAUDE_CODE_OAUTH_TOKEN != 'true' &&
(env.HAS_ANTHROPIC_API_KEY != 'true' || env.HAS_ANTHROPIC_BASE_URL != 'true')
}}
if: ${{ env.ANTHROPIC_API_KEY == '' || env.ANTHROPIC_BASE_URL == '' }}
run: echo "Claude Code review secrets are not configured; skipping Claude Code review."

- name: Detect Claude review workflow changes
id: claude-workflow-change
if: >-
${{
env.HAS_CLAUDE_CODE_OAUTH_TOKEN == 'true' ||
(env.HAS_ANTHROPIC_API_KEY == 'true' && env.HAS_ANTHROPIC_BASE_URL == 'true')
}}
if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' }}
uses: actions/github-script@v8
with:
script: |
Expand All @@ -60,58 +51,14 @@ jobs:
run: echo "Skipping Claude Code Review because this PR changes the review workflow itself."

- name: Checkout repository
if: >-
${{
(
env.HAS_CLAUDE_CODE_OAUTH_TOKEN == 'true' ||
(env.HAS_ANTHROPIC_API_KEY == 'true' && env.HAS_ANTHROPIC_BASE_URL == 'true')
) &&
steps.claude-workflow-change.outputs.self_changed != 'true'
}}
if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && steps.claude-workflow-change.outputs.self_changed != 'true' }}
uses: actions/checkout@v6
with:
fetch-depth: 1
persist-credentials: false

- name: Run Claude Code review with OAuth
if: ${{ env.HAS_CLAUDE_CODE_OAUTH_TOKEN == 'true' && steps.claude-workflow-change.outputs.self_changed != 'true' }}
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
track_progress: true
use_sticky_comment: true
exclude_comments_by_actor: MapleEve,github-actions,codecov,sourcery-ai,copilot-pull-request-reviewer
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}

Review this pull request using REVIEW.md as the review-only guide.
Focus on actionable VoScript risks:
- Privacy and security leaks
- Model lifecycle races and GPU/CPU fallback behavior
- HTTP API compatibility
- Regression-test coverage
- Synchronized English/Chinese documentation

The PR branch is already checked out in the current working directory.
Post feedback only through the official Claude Code Action GitHub integration.
Do not use the GitHub CLI and do not use a user-owned GitHub token.
If the official Claude GitHub App integration is unavailable, fail instead of posting as the repository owner.
If there are no actionable findings, post the standard no-findings confirmation through the action integration.
Avoid formatting-only comments.

claude_args: |
--model ${{ env.CLAUDE_MODEL }}
--max-turns 30

- name: Run Claude Code review with API key
if: >-
${{
env.HAS_CLAUDE_CODE_OAUTH_TOKEN != 'true' &&
env.HAS_ANTHROPIC_API_KEY == 'true' &&
env.HAS_ANTHROPIC_BASE_URL == 'true' &&
steps.claude-workflow-change.outputs.self_changed != 'true'
}}
- name: Run Claude Code review
if: ${{ env.ANTHROPIC_API_KEY != '' && env.ANTHROPIC_BASE_URL != '' && steps.claude-workflow-change.outputs.self_changed != 'true' }}
uses: anthropics/claude-code-action@v1
env:
ANTHROPIC_BASE_URL: ${{ secrets.ANTHROPIC_BASE_URL }}
Expand Down
138 changes: 90 additions & 48 deletions .github/workflows/issue-autocomment.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Issue Auto Comment

on:
issues:
types:
Expand All @@ -16,58 +17,99 @@ permissions:
jobs:
run:
permissions:
issues: write # for actions-cool/issues-helper to update issues
pull-requests: write # for actions-cool/issues-helper to update PRs
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Auto Comment on Issues Opened
uses: wow-actions/auto-comment@v1
- name: Comment and maintain labels
uses: actions/github-script@v8
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
issuesOpened: |
👀 @{{ author }}
script: |
const { owner, repo } = context.repo;

Thank you for raising an issue. We will investigate into the matter and get back to you as soon as possible.
Please make sure you have given us as much context as possible.\
非常感谢您提交 issue。我们会尽快调查此事,并尽快回复您。 请确保您已经提供了尽可能多的背景信息。
- name: Auto Comment on Issues Closed
uses: wow-actions/auto-comment@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
issuesClosed: |
✅ @{{ author }}
async function createComment(issueNumber, body) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body,
});
}

This issue is closed, If you have any questions, you can comment and reply.\
此问题已经关闭。如果您有任何问题,可以留言并回复。
- name: Auto Comment on Pull Request Opened
uses: wow-actions/auto-comment@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}}
pullRequestOpened: |
👍 @{{ author }}
if (context.eventName === "issues") {
const issue = context.payload.issue;
const actor = context.actor;
const author = issue.user.login;

Thank you for raising your pull request and contributing to voscript.
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
If you encounter any problems, please feel free to connect with us.\
非常感谢您提出拉取请求并为 voscript 做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。
如果您遇到任何问题,请随时与我们联系。
- name: Auto Comment on Pull Request Merged
uses: actions-cool/pr-welcome@main
if: github.event.pull_request.merged == true
with:
token: ${{ secrets.GITHUB_TOKEN }}
comment: |
❤️ Great PR @${{ github.event.pull_request.user.login }} ❤️
if (context.payload.action === "opened") {
await createComment(
issue.number,
[
`👀 @${author}`,
"",
"Thank you for raising an issue. We will investigate into the matter and get back to you as soon as possible.",
"Please make sure you have given us as much context as possible.",
"非常感谢您提交 issue。我们会尽快调查此事,并尽快回复您。请确保您已经提供了尽可能多的背景信息。",
].join("\n"),
);
}

The growth of project is inseparable from user feedback and contribution, thanks for your contribution! \
项目的成长离不开用户反馈和贡献,感谢您的贡献!
emoji: 'hooray'
pr-emoji: '+1, heart'
- name: Remove inactive
if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
uses: actions-cool/issues-helper@v3
with:
actions: 'remove-labels'
token: ${{ secrets.GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: 'Inactive'
if (context.payload.action === "closed") {
await createComment(
issue.number,
[
`✅ @${author}`,
"",
"This issue is closed. If you have any questions, you can comment and reply.",
"此问题已经关闭。如果您有任何问题,可以留言并回复。",
].join("\n"),
);
}

if (issue.state === "open" && actor === author) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issue.number,
name: "Inactive",
});
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
}

if (context.eventName === "pull_request_target") {
const pull = context.payload.pull_request;
const author = pull.user.login;

if (context.payload.action === "opened") {
await createComment(
pull.number,
[
`👍 @${author}`,
"",
"Thank you for raising your pull request and contributing to VoScript.",
"Please make sure you have followed our contributing guidelines. We will review it as soon as possible.",
"If you encounter any problems, please feel free to connect with us.",
"非常感谢您提出拉取请求并为 VoScript 做出贡献,请确保您已经遵循了我们的贡献指南,我们会尽快审查它。",
"如果您遇到任何问题,请随时与我们联系。",
].join("\n"),
);
}

if (context.payload.action === "closed" && pull.merged) {
await createComment(
pull.number,
[
`❤️ Great PR @${author} ❤️`,
"",
"The growth of the project is inseparable from user feedback and contribution. Thanks for your contribution!",
"项目的成长离不开用户反馈和贡献,感谢您的贡献!",
].join("\n"),
);
}
}
2 changes: 1 addition & 1 deletion .github/workflows/rust-foundation-heavy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:
docker run --rm \
-e RUST_KERNEL_MODE=required \
voscript-rust-foundation:${{ github.sha }} \
python -c "from providers.kernel_bridge import core_smoke, postprocess_segments, voiceprint_score; result = core_smoke({'source': 'ci'}); assert result['ok'] is True; assert result['echoed']['source'] == 'ci'; decision = voiceprint_score({'query_embedding': [1.0, 0.0], 'candidates': [{'speaker_id': 'spk_ci', 'name': 'CI', 'embedding': [1.0, 0.0], 'sample_count': 1, 'sample_spread': None}], 'threshold': 0.75, 'asnorm_threshold': 0.5, 'cohort': None}); assert decision['matched_id'] == 'spk_ci'; assert decision['reason'] == 'matched'; processed = postprocess_segments({'aligned_segments': [{'start': 0.0, 'end': 1.0, 'text': 'hello', 'speaker': 'SPEAKER_00'}], 'speaker_map': {}}); assert processed['segments'][0]['speaker_label'] == 'SPEAKER_00'; assert processed['unique_speakers'] == ['SPEAKER_00']"
python -c "from providers.kernel_bridge import artifact_manifest_contract, core_smoke, postprocess_segments, status_payload_contract, voiceprint_score; result = core_smoke({'source': 'ci'}); assert result['ok'] is True; assert result['echoed']['source'] == 'ci'; decision = voiceprint_score({'query_embedding': [1.0, 0.0], 'candidates': [{'speaker_id': 'spk_ci', 'name': 'CI', 'embedding': [1.0, 0.0], 'sample_count': 1, 'sample_spread': None}], 'threshold': 0.75, 'asnorm_threshold': 0.5, 'cohort': None}); assert decision['matched_id'] == 'spk_ci'; assert decision['reason'] == 'matched'; processed = postprocess_segments({'aligned_segments': [{'start': 0.0, 'end': 1.0, 'text': 'hello', 'speaker': 'SPEAKER_00'}], 'speaker_map': {}}); assert processed['segments'][0]['speaker_label'] == 'SPEAKER_00'; assert processed['unique_speakers'] == ['SPEAKER_00']; manifest = artifact_manifest_contract({'manifest_version': 'artifact_manifest.v1', 'stable': [{'name': 'result', 'filename': 'result.json', 'role': 'primary_result', 'media_type': 'application/json', 'required_for_result': True}], 'optional': [], 'experimental': []}); assert manifest['stable'][0]['filename'] == 'result.json'; status = status_payload_contract({'status': 'queued', 'updated_at': '2026-06-09T00:00:00+00:00', 'filename': 'private/audio.wav'}); assert status['status'] == 'queued'; assert status['filename'] == 'audio.wav'"

- name: Run health check smoke
run: |
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion app/api/routers/transcriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from infra.job_persistence import _atomic_write_json, _write_status
from infra.job_runtime import jobs, register_in_flight, unregister_in_flight
from pipeline.contracts import normalize_status_payload

_SPK_ID_RE = re.compile(r"^spk_[A-Za-z0-9_-]{1,64}$")

Expand Down Expand Up @@ -247,7 +248,7 @@ async def get_job(

if status_path.exists():
try:
status_data = json.loads(status_path.read_text())
status_data = normalize_status_payload(json.loads(status_path.read_text()))
except Exception:
raise HTTPException(404, "Job not found")

Expand Down
31 changes: 19 additions & 12 deletions app/infra/job_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
from pathlib import Path

from config import TRANSCRIPTIONS_DIR
from pipeline.contracts import (
TERMINAL_JOB_STATUSES,
build_status_payload,
normalize_status_payload,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -49,13 +54,12 @@ def _write_status(
"""
status_path = TRANSCRIPTIONS_DIR / job_id / "status.json"
try:
payload = {
"status": status,
"updated_at": datetime.now(tz=timezone.utc).isoformat(),
"error": error,
}
if filename is not None:
payload["filename"] = filename
payload = build_status_payload(
status,
error=error,
filename=filename,
updated_at=datetime.now(tz=timezone.utc).isoformat(),
)
_atomic_write_json(status_path, payload)
return True
except Exception as exc:
Expand All @@ -68,11 +72,14 @@ def recover_orphan_jobs() -> None:
try:
for status_path in TRANSCRIPTIONS_DIR.glob("*/status.json"):
try:
data = json.loads(status_path.read_text())
if data.get("status") not in ("completed", "failed"):
data["status"] = "failed"
data["error"] = "Process restarted while job was in progress"
data["updated_at"] = datetime.now(tz=timezone.utc).isoformat()
data = normalize_status_payload(json.loads(status_path.read_text()))
if data.get("status") not in TERMINAL_JOB_STATUSES:
data = build_status_payload(
"failed",
error="Process restarted while job was in progress",
filename=data.get("filename"),
updated_at=datetime.now(tz=timezone.utc).isoformat(),
)
_atomic_write_json(status_path, data)
logger.info(
"AR-C2: marked orphan job %s as failed",
Expand Down
Loading
Loading