Skip to content
Closed
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
235 changes: 210 additions & 25 deletions .github/workflows/opencode-review.yml

Large diffs are not rendered by default.

416 changes: 416 additions & 0 deletions .github/workflows/strix.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/desktop/src/features/workspace/RoleSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function RoleSwitcher({ roles, activeRole, onRoleChange }: RoleSwitcherPr
return (
<div className="flex flex-col gap-4 py-2 sm:flex-row sm:items-center">
<div className="flex whitespace-nowrap text-sm font-semibold text-slate-200">
<Users className="mr-2 size-4 text-cyan-300" />
<Users className="mr-2 size-4 text-cyan-300" aria-hidden="true" />
{t("roleSwitcherTitle")}
</div>
<Tabs
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/features/workspace/SectionRoadmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,14 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma

{role.setupNote && (
<div className="flex items-start gap-2 rounded-md border border-amber-300/20 bg-amber-300/[0.08] p-2 text-xs font-medium text-amber-100">
<Lightbulb className="mt-0.5 size-3.5 shrink-0" />
<Lightbulb className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{role.setupNote}</span>
</div>
)}

{role.simplification && (
<div className="flex items-start gap-2 rounded-md border border-indigo-300/20 bg-indigo-300/[0.08] p-2 text-xs font-medium text-indigo-100">
<Wand2 className="mt-0.5 size-3.5 shrink-0" />
<Wand2 className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{role.simplification}</span>
</div>
)}
Expand All @@ -181,7 +181,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma
<div className="mt-2 space-y-1.5">
{role.overlapWarnings.map((warning, wIdx) => (
<div key={wIdx} className="flex items-start gap-2 rounded-md border border-rose-300/20 bg-rose-300/[0.08] p-2 text-xs font-medium text-rose-100">
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
<AlertCircle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{warning}</span>
</div>
))}
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/features/workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportCueSheet}
className="min-h-10 border-cyan-300/30 bg-cyan-300/10 font-semibold text-cyan-50 shadow-[0_10px_30px_rgba(34,211,238,0.16)] hover:bg-cyan-300/20 hover:text-white"
>
<Download className="mr-2 size-4 text-cyan-200" />
<Download className="mr-2 size-4 text-cyan-200" aria-hidden="true" />
Export Cue Sheet (CSV)
</Button>
<Button
Expand All @@ -231,7 +231,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportChart}
className="min-h-10 border-white/10 bg-white/5 font-semibold text-slate-100 shadow-sm hover:bg-white/10 hover:text-white"
>
<Download className="mr-2 size-4 text-slate-300" />
<Download className="mr-2 size-4 text-slate-300" aria-hidden="true" />
Export Chart (JSON)
</Button>
<Button
Expand All @@ -240,7 +240,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportHandoff}
className="min-h-10 border-teal-300/25 bg-teal-300/10 font-semibold text-teal-50 shadow-sm hover:bg-teal-300/20 hover:text-white"
>
<Download className="mr-2 size-4 text-teal-200" />
<Download className="mr-2 size-4 text-teal-200" aria-hidden="true" />
Export Handoff (JSON)
</Button>
</div>
Expand Down
2,387 changes: 2,387 additions & 0 deletions requirements-strix-ci-hashes.txt

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions requirements-strix-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
strix-agent==1.0.4
google-cloud-aiplatform==1.133.0
cryptography==49.0.0
python-multipart==0.0.31
61 changes: 11 additions & 50 deletions scripts/checks/verify_supply_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1216,30 +1216,23 @@ def is_blocking_required_step(block_lines: list[str], block_indent: int) -> bool
return []


def _verify_ci_coverage(missing: list[str]) -> None:
def verify_workflow_coverage() -> list[str]:
"""Return workflow trigger and artifact coverage violations."""
missing: list[str] = []
ci = read_workflow(Path(".github/workflows/ci.yml"), "ci", missing)
for token in ["develop", "main", "pull_request", "push", "ci / build-and-test"]:
if ci and token not in ci:
missing.append(f"ci workflow missing token: {token}")


def _verify_sbom_coverage(missing: list[str]) -> None:
sbom = read_workflow(Path(".github/workflows/sbom.yml"), "sbom", missing)
for token in ["develop", "main", "pull_request", "release:", "tags:"]:
if sbom and token not in sbom:
missing.append(f"sbom workflow missing trigger token: {token}")


def _verify_dependency_review_coverage(missing: list[str]) -> None:
review = read_workflow(
Path(".github/workflows/dependency-review.yml"), "dependency review", missing
)
for token in ["develop", "main", "pull_request"]:
if review and token not in review:
missing.append(f"dependency review workflow missing trigger token: {token}")


def _verify_security_audit_coverage(missing: list[str]) -> None:
audit = read_workflow(
Path(".github/workflows/security-audit.yml"), "security audit", missing
)
Expand All @@ -1266,16 +1259,10 @@ def _verify_security_audit_coverage(missing: list[str]) -> None:
missing.append(
f"security audit workflow missing vulnerability audit token: {token}"
)


def _verify_codeql_coverage(missing: list[str]) -> None:
codeql = read_workflow(Path(".github/workflows/codeql.yml"), "codeql", missing)
for token in ["develop", "main", "pull_request", "push", "codeql"]:
if codeql and token not in codeql:
missing.append(f"codeql workflow missing token: {token}")


def _verify_release_coverage(missing: list[str]) -> None:
release = read_workflow(Path(".github/workflows/release.yml"), "release", missing)
for token in [
"develop",
Expand All @@ -1287,18 +1274,12 @@ def _verify_release_coverage(missing: list[str]) -> None:
]:
if release and token not in release:
missing.append(f"release workflow missing token: {token}")


def _verify_secret_scan_coverage(missing: list[str]) -> None:
secret_scan = read_workflow(
Path(".github/workflows/secret-scan-gate.yml"), "secret scan", missing
)
for token in ["develop", "main", "pull_request", "push", "secret-scan-gate"]:
if secret_scan and token not in secret_scan:
missing.append(f"secret scan workflow missing token: {token}")


def _verify_build_coverage(missing: list[str]) -> None:
build = read_workflow(
Path(".github/workflows/build-baseline.yml"), "build baseline", missing
)
Expand Down Expand Up @@ -1336,9 +1317,14 @@ def _verify_build_coverage(missing: list[str]) -> None:
missing.append(
"build workflow should not rely on macos-latest for architecture coverage"
)


def _verify_scorecard_coverage(missing: list[str], workflow_paths: list[Path]) -> None:
workflow_paths = sorted(Path(".github/workflows").glob("*.yml")) + sorted(
Path(".github/workflows").glob("*.yaml")
)
for workflow_path in workflow_paths:
workflow_content = workflow_path.read_text(encoding="utf-8")
missing.extend(
release_artifact_download_decompression_violations(workflow_content)
)
scorecard = read_workflow(
Path(".github/workflows/ossf-scorecard.yml"), "ossf scorecard", missing
)
Expand Down Expand Up @@ -1381,31 +1367,6 @@ def _verify_scorecard_coverage(missing: list[str], workflow_paths: list[Path]) -
workflow_content, workflow_path
)
)


def verify_workflow_coverage() -> list[str]:
"""Return workflow trigger and artifact coverage violations."""
missing: list[str] = []
_verify_ci_coverage(missing)
_verify_sbom_coverage(missing)
_verify_dependency_review_coverage(missing)
_verify_security_audit_coverage(missing)
_verify_codeql_coverage(missing)
_verify_release_coverage(missing)
_verify_secret_scan_coverage(missing)
_verify_build_coverage(missing)

workflow_paths = sorted(Path(".github/workflows").glob("*.yml")) + sorted(
Path(".github/workflows").glob("*.yaml")
)
for workflow_path in workflow_paths:
workflow_content = workflow_path.read_text(encoding="utf-8")
missing.extend(
release_artifact_download_decompression_violations(workflow_content)
)

_verify_scorecard_coverage(missing, workflow_paths)

return missing


Expand Down
69 changes: 56 additions & 13 deletions scripts/ci/opencode_review_approve_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ if [ -z "$CONTROL_JSON" ]; then
fi

TMP_JSON="$(mktemp)"
trap 'rm -f "$TMP_JSON" "${TMP_JSON}.normalized"' EXIT
trap 'rm -f "$TMP_JSON"' EXIT
printf '%s\n' "$CONTROL_JSON" >"$TMP_JSON"

if ! jq -e . "$TMP_JSON" >/dev/null 2>&1; then
Expand All @@ -84,12 +84,6 @@ CONTROL_RUN_ID="$(jq -r '.run_id // empty' "$TMP_JSON")"
CONTROL_RUN_ATTEMPT="$(jq -r '.run_attempt // empty' "$TMP_JSON")"
RESULT="$(jq -r '.result // empty' "$TMP_JSON")"

if [ "$RESULT" = "APPROVE" ]; then
TMP_NORMALIZED_JSON="${TMP_JSON}.normalized"
jq '.findings = (.findings // [])' "$TMP_JSON" >"$TMP_NORMALIZED_JSON"
mv "$TMP_NORMALIZED_JSON" "$TMP_JSON"
fi

if [ "$CONTROL_HEAD_SHA" != "$EXPECTED_HEAD_SHA" ]; then
echo "SHA_MISMATCH"
exit 3
Expand All @@ -113,13 +107,12 @@ if ! jq -e '
and (.result == "APPROVE" or .result == "REQUEST_CHANGES")
and (.reason | type == "string" and length > 0)
and (.summary | type == "string" and length > 0)
and (.findings | type == "array")
and (
if .result == "REQUEST_CHANGES" then (.findings | length > 0)
else (.findings | length == 0)
if .result == "REQUEST_CHANGES" then (.findings | type == "array" and length > 0)
else ((.findings == null) or (.findings | type == "array" and length == 0))
end
)
and all(.findings[];
and all((.findings // [])[];
(.path | type == "string" and length > 0)
and ((.path | ascii_downcase) as $p | ($p != "n/a" and $p != "unknown"))
and (.line | type == "number" and . > 0 and floor == .)
Expand All @@ -142,19 +135,26 @@ if ! python3 "$NORMALIZER" --check-structural-approval "$TMP_JSON" >/dev/null; t
exit 4
fi

SOURCE_ROOT="${GITHUB_WORKSPACE:-$PWD}"
SOURCE_ROOT="${OPENCODE_SOURCE_WORKDIR:-${GITHUB_WORKSPACE:-$PWD}}"
if ! python3 - "$SOURCE_ROOT" "$TMP_JSON" <<'PY'
from __future__ import annotations

import json
import os
import re
import subprocess
import sys
from pathlib import Path


source_root = Path(sys.argv[1]).resolve()
control_file = Path(sys.argv[2])
control = json.loads(control_file.read_text(encoding="utf-8"))
pr_base_sha = os.environ.get("PR_BASE_SHA", "").strip()
pr_head_sha = (
os.environ.get("PR_HEAD_SHA", "").strip()
or os.environ.get("HEAD_SHA", "").strip()
)

if control.get("result") != "REQUEST_CHANGES":
raise SystemExit(0)
Expand All @@ -164,6 +164,47 @@ def normalized_line(value: str) -> str:
return " ".join(value.strip().split())


def changed_new_lines(path_value: str) -> set[int]:
if not pr_base_sha or not pr_head_sha:
return set()
try:
completed = subprocess.run(
[
"git",
"-C",
str(source_root),
"diff",
"--unified=0",
"--no-ext-diff",
pr_base_sha,
pr_head_sha,
"--",
path_value,
],
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
except OSError:
return set()
if completed.returncode not in {0, 1}:
return set()

line_numbers: set[int] = set()
hunk_header = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
for raw_line in completed.stdout.splitlines():
match = hunk_header.match(raw_line)
if not match:
continue
start = int(match.group(1))
count = int(match.group(2) or "1")
if count <= 0:
continue
line_numbers.update(range(start, start + count))
return line_numbers


def finding_is_source_backed(finding: dict[str, object]) -> bool:
path_value = str(finding.get("path", ""))
if (
Expand All @@ -190,6 +231,8 @@ def finding_is_source_backed(finding: dict[str, object]) -> bool:
line_number = finding.get("line")
if not isinstance(line_number, int) or line_number < 1 or line_number > len(source_lines):
return False
if line_number not in changed_new_lines(path_value):
return False

source_line_set = {
normalized_line(line)
Expand Down Expand Up @@ -228,7 +271,7 @@ then
fi

if [ -n "$NORMALIZED_JSON_FILE" ]; then
jq -c '{head_sha, run_id, run_attempt, result, reason, summary, findings}' "$TMP_JSON" >"$NORMALIZED_JSON_FILE"
jq -c '{head_sha, run_id, run_attempt, result, reason, summary, findings:(.findings // [])}' "$TMP_JSON" >"$NORMALIZED_JSON_FILE"
fi

echo "$RESULT"
Expand Down
Loading
Loading