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
29 changes: 15 additions & 14 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# Pull Request Description

## Summary
<!-- What does this PR do? -->

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
<!-- What does this PR do, in 1-3 sentences -->

## Changes

<!-- Bullet list of key changes -->
-

## Testing
<!-- How was this tested? -->

## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Tests added/updated
- [ ] Documentation updated
<!-- How was this verified? -->
- [ ] Local lint passes
- [ ] Local tests pass
- [ ] Manual smoke test (if applicable)

## Related

<!-- Issues/PRs/specs this addresses -->
Closes #
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ worktrees/
docs/.vitepress/cache
docs/.vitepress/dist
node_modules

# grade reports (build artifacts)
.grade-reports/
2 changes: 2 additions & 0 deletions LICENSE-APACHE
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Apache-2.0 license text placeholder.
See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
21 changes: 21 additions & 0 deletions LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Koosha Pari

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
27 changes: 27 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,30 @@ tasks:
if pyc_file.is_file():
pyc_file.unlink()
PY
grade:
desc: Full grade (strictest — no caching)
cmds:
- ./grade.sh

grade-fast:
desc: Fast grade (skips heavy checks)
cmds:
- ./grade.sh --fast

grade-json:
desc: Grade with JSON output
cmds:
- ./grade.sh --json

grade-html:
desc: Grade with HTML report
cmds:
- ./grade.sh --html

install-lefthook:
desc: Install lefthook git hooks
cmds:
- lefthook install
status:
- test -f .git/hooks/lefthook

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong lefthook install probe

Low Severity

The install-lefthook task’s status checks for .git/hooks/lefthook, but lefthook install installs standard hook entrypoints like pre-commit and commit-msg, not a file named lefthook. The status check never succeeds, so the task always reruns install.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 48a9306. Configure here.


33 changes: 33 additions & 0 deletions docs/SSOT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# SSOT — PolicyStack

## State
- Default branch: main
- Last verified: 2026-06-08
- CI status: green
- Open PRs: 0
- Open branches: 1 (main)
- Stashes: 0

## Dependencies
- Rust: N/A
- Node: 20
- Python: N/A

## Architecture
- Hexagonal: in progress
- Ports: N/A
- Adapters: N/A
- Domain: N/A

## Next Steps
1. [x] P0: State unification
2. [x] P1: Tooling + governance
3. [ ] P2: Hexagonal refactor
4. [ ] P3: Add tests
5. [ ] P4: Add CI

## Fleet Links
- Parent: Phenotype
- Related: N/A
- Consumes: N/A
- Merged into: N/A
Comment on lines +1 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Move this tracker document from docs/SSOT.md into the appropriate designated docs/ subdirectory (for example docs/reference/ or another tracker-specific folder) so it is not created at the top level of docs/. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The file is a tracker-style Markdown document (state, next steps, fleet links) and it lives directly under docs/ instead of a designated subdirectory. The rule says trackers and other Markdown docs should be placed under docs/ in the appropriate subdirectory, so this is a real location violation.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** docs/SSOT.md
**Line:** 1:33
**Comment:**
	*Custom Rule: Move this tracker document from `docs/SSOT.md` into the appropriate designated `docs/` subdirectory (for example `docs/reference/` or another tracker-specific folder) so it is not created at the top level of `docs/`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Empty file.
198 changes: 198 additions & 0 deletions grade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/usr/bin/env bash
# grade.sh — Fleet-wide project grading engine
# Usage: ./grade.sh [--fast] [--json] [--html]
# --fast : Quick mode (skips heavy checks like fuzz, mutation, perf)
# --json : Output machine-readable JSON
# --html : Output HTML report

set -euo pipefail

FAST=false
JSON=false
HTML=false
REPORT_DIR=".grade-reports"

while [[ $# -gt 0 ]]; do
case "$1" in
--fast) FAST=true; shift ;;
--json) JSON=true; shift ;;
--html) HTML=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done

mkdir -p "$REPORT_DIR"

# Detect stack
STACK="unknown"
if [[ -f "Cargo.toml" ]]; then STACK="rust"; fi
if [[ -f "package.json" ]]; then STACK="node"; fi
if [[ -f "pyproject.toml" || -f "setup.py" ]]; then STACK="python"; fi
if [[ -f "go.mod" ]]; then STACK="go"; fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stack detection last file wins

Medium Severity

grade.sh sets STACK with independent if statements, so later markers overwrite earlier ones. This repo has both package.json and pyproject.toml, so grading runs only the Python suite with hardcoded src/ paths and pip install -e '.[dev]', which do not match PolicyStack’s layout or pyproject.toml.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 48a9306. Configure here.

if [[ -f "Taskfile.yml" || -f "Justfile" ]]; then
: # already has task runner
fi

SCORE=0
MAX=0
CHECKS=()

run_check() {
local name="$1"
local cmd="$2"
local weight="${3:-1}"
local fast_skip="${4:-false}"

if [[ "$FAST" == true && "$fast_skip" == true ]]; then
CHECKS+=("{\"name\":\"$name\",\"status\":\"skipped\",\"score\":0,\"max\":$weight,\"detail\":\"skipped in fast mode\"}")
return 0
fi

MAX=$((MAX + weight))
if eval "$cmd" 2>&1 | tee "$REPORT_DIR/${name}.log" >"$REPORT_DIR/${name}.raw" 2>&1; then
Comment on lines +46 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: In fast mode, skipped checks keep their original max value in per-check records but are excluded from global MAX, so report totals become inconsistent and percentages are not comparable to the check table. Set skipped check max to 0 or include skipped weights in global totals consistently. [logic error]

Severity Level: Major ⚠️
- ⚠️ `grade-fast` summaries inconsistent with per-check weights and scores.
- ⚠️ Developers misjudge quality when skipped checks inflated aggregate percentage.
Steps of Reproduction ✅
1. Run fast grading through `task grade-fast` (Task defined at
`/workspace/PolicyStack/Taskfile.yml:217-220`, executing `./grade.sh --fast`) or via the
lefthook `pre-commit.test-fast` command at `/workspace/PolicyStack/lefthook.yml:35-44`,
which calls `task grade-fast` when source files change.

2. With `--fast`, `grade.sh` sets `FAST=true` in its argument parsing (lines `15-22`), and
then processes checks for the detected stack (e.g., Python at `grade.sh:96-108` or Go at
`70-79`), some of which are marked as heavy by passing `true` as the `fast_skip` argument.

3. For any heavy check (such as Python coverage at `grade.sh:66` or Go race tests at `76`)
where `fast_skip=true`, the `if` block at `grade.sh:46-48` executes: it appends a JSON
record with `"status":"skipped","score":0,"max":$weight` to `CHECKS` and returns early
without executing the command.

4. Because these skipped checks return before line `51`, their weights are not added to
the global `MAX` accumulator, so the summary percentage computed at `grade.sh:127-132`
uses a denominator that excludes skipped checks, while the per-check records in JSON/HTML
(constructed at `141-157` and `162-193`) still display the original `max` values for
skipped checks, making the aggregate `SCORE/MAX` percentage inconsistent with the detailed
check weights developers see in reports.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** grade.sh
**Line:** 46:52
**Comment:**
	*Logic Error: In fast mode, skipped checks keep their original `max` value in per-check records but are excluded from global `MAX`, so report totals become inconsistent and percentages are not comparable to the check table. Set skipped check `max` to `0` or include skipped weights in global totals consistently.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

SCORE=$((SCORE + weight))
CHECKS+=("{\"name\":\"$name\",\"status\":\"pass\",\"score\":$weight,\"max\":$weight,\"detail\":\"\"}")
echo " [PASS] $name"
else
local detail="$(head -5 "$REPORT_DIR/${name}.raw" | tr '\n' ' ')"
CHECKS+=("{\"name\":\"$name\",\"status\":\"fail\",\"score\":0,\"max\":$weight,\"detail\":\"$detail\"}")
Comment on lines +57 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Failed command output is injected into JSON strings without escaping, so any quote or backslash in command output can produce invalid JSON in reports. Escape detail properly (or build JSON with a serializer) before appending to CHECKS. [type error]

Severity Level: Major ⚠️
-`task grade-json` may emit invalid JSON grade report files.
- ⚠️ Downstream JSON consumers break parsing on errorful grade runs.
Steps of Reproduction ✅
1. Invoke JSON grading via `task grade-json` from the project root (Task `grade-json` is
defined at `/workspace/PolicyStack/Taskfile.yml:222-225` and runs `./grade.sh --json`),
which sets `JSON=true` in `grade.sh` argument parsing at lines `15-22`.

2. Ensure at least one check fails; for example, because there is no `src/` directory, the
Python lint/typecheck/security checks at `grade.sh:99-101,105-106` fail when
`STACK="python"`, writing error output into `.grade-reports/<name>.raw`.

3. On each failure, `run_check` at `grade.sh:57-58` reads the first five lines of the
command's raw output into `detail` using `head -5 "$REPORT_DIR/${name}.raw" | tr '\n' '
'`, then interpolates `$detail` directly into a JSON-like string appended to the `CHECKS`
array, without escaping embedded quotes or backslashes from the tool output.

4. When the JSON block at `grade.sh:142-157` writes `$REPORT_DIR/grade.json`, it inlines
`$(IFS=,; echo "${CHECKS[*]}")` into the `"checks"` array; any `detail` containing
characters such as `"` or `\` (common in CLI error messages) produces syntactically
invalid JSON, so the generated `grade.json` cannot be parsed reliably by downstream tools.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** grade.sh
**Line:** 57:58
**Comment:**
	*Type Error: Failed command output is injected into JSON strings without escaping, so any quote or backslash in command output can produce invalid JSON in reports. Escape `detail` properly (or build JSON with a serializer) before appending to `CHECKS`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

echo " [FAIL] $name"
fi
}

echo "========================================"
echo " GRADE — $(basename "$(pwd)")"
echo " Stack: $STACK"
echo " Mode: $([[ $FAST == true ]] && echo fast || echo full)"
echo "========================================"

case "$STACK" in
rust)
run_check "build" "cargo build --workspace" 2
run_check "test-unit" "cargo test --workspace" 3
run_check "fmt" "cargo fmt -- --check" 2
run_check "clippy" "cargo clippy --workspace --all-targets --all-features -- -D warnings" 2
run_check "deny" "cargo deny check" 1 true
run_check "doc" "cargo doc --workspace --no-deps" 1
run_check "test-snapshot" "cargo test --workspace -- snapshot" 1 true
run_check "test-fuzz" "cargo test --workspace -- fuzz" 1 true
run_check "coverage" "cargo llvm-cov --workspace --fail-under-lines 85" 2 true
run_check "audit" "cargo audit" 1 true
run_check "bench" "cargo bench --workspace" 1 true
;;
node)
run_check "install" "npm ci" 1
run_check "build" "npm run build" 2
run_check "test-unit" "npm test" 3
run_check "lint" "npx eslint . --ext .ts" 2
run_check "fmt" "npx prettier --check '**/*.ts'" 2
run_check "typecheck" "npx tsc --noEmit" 2
run_check "test-e2e" "npm run test:e2e" 2 true
run_check "test-perf" "npm run test:perf" 1 true
run_check "test-mutation" "npx stryker run" 1 true
run_check "coverage" "npx jest --coverage --coverageThreshold='{\"global\":{\"branches\":85,\"functions\":85,\"lines\":85,\"statements\":85}}'" 2 true
run_check "audit" "npm audit --audit-level=moderate" 1
;;
python)
run_check "install" "pip install -e '.[dev]'" 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The Python install step calls pip install -e '.[dev]', but this repository's pyproject.toml does not define a dev extra, so the install check will fail immediately and force the whole grade to fail. Use the project's actual dependency workflow (the repo already uses uv in Taskfile) or install without a nonexistent extra. [api mismatch]

Severity Level: Major ⚠️
- ❌ Python install check always fails due to missing 'dev' extra.
- ⚠️ Grade summary includes avoidable failure, reducing trust in tool.
Steps of Reproduction ✅
1. From the repository root `/workspace/PolicyStack`, note that `pyproject.toml` exists
(verified at `/workspace/PolicyStack/pyproject.toml:1-8`), so `grade.sh` will detect
`STACK=\"python\"` via the stack detection logic at `grade.sh:26-31`.

2. Run `task grade` (Taskfile target defined at
`/workspace/PolicyStack/Taskfile.yml:212-215`), which executes `./grade.sh` and therefore
enters the `python)` case in `grade.sh` at lines `96-108`.

3. Inside the Python case, the install check at `grade.sh:97` runs `run_check "install"
"pip install -e '.[dev]'" 1`, invoking `pip install -e '.[dev]'` in the project root.

4. Inspect `pyproject.toml` at `/workspace/PolicyStack/pyproject.toml:5-32` and observe
there is no `[project.optional-dependencies]` or `dev` extra defined, so `pip install -e
'.[dev]'` fails on every run; `run_check` treats the non-zero exit as a failed check, and
`grade.sh` always records the Python install step as `[FAIL] install` for this repository.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** grade.sh
**Line:** 97:97
**Comment:**
	*Api Mismatch: The Python install step calls `pip install -e '.[dev]'`, but this repository's `pyproject.toml` does not define a `dev` extra, so the install check will fail immediately and force the whole grade to fail. Use the project's actual dependency workflow (the repo already uses `uv` in Taskfile) or install without a nonexistent extra.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

run_check "test-unit" "pytest -v" 3
run_check "lint" "ruff check src" 2
run_check "fmt" "ruff format --check src" 2
run_check "typecheck" "mypy src" 2
run_check "test-fuzz" "pytest -v --fuzz" 1 true
run_check "test-mutation" "mutmut run" 1 true
run_check "test-perf" "pytest -v --perf" 1 true
run_check "coverage" "pytest --cov=src --cov-report=term-missing --cov-fail-under=85" 2 true
run_check "security" "bandit -r src" 1
Comment on lines +99 to +106

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: These Python checks are hardcoded to src, but this repo has no top-level src/ directory, so lint/typecheck/security/coverage checks fail due to wrong paths rather than real quality issues. Point checks at the real Python locations used by the project. [incorrect variable usage]

Severity Level: Critical 🚨
- ❌ Pre-push `task grade` fails from ruff,mypy,bandit on src.
- ⚠️ Developers blocked pushing despite healthy tests and real sources.
Steps of Reproduction ✅
1. List the project root (`/workspace/PolicyStack`) and observe there is no top-level
`src/` directory (confirmed by `ls` output), while Python sources live in files like
`policy_lib.py`, `resolve.py`, and under `cli/src/policy_federation` (see
`/workspace/PolicyStack/cli/src/policy_federation/*.py` from the Glob results).

2. Confirm the project is Python-based via `Taskfile.yml:8-29`, where `PROJECT_LANG`
chooses `python` when `pyproject.toml` exists, and note the Python build/test tasks set
`PYTHONPATH` to include `cli/src` (e.g., `Taskfile.yml:52-56` and `100-104`), reinforcing
that the real source tree is not `src/`.

3. Run `task grade` (defined at `/workspace/PolicyStack/Taskfile.yml:212-215`) or trigger
the pre-push hook defined in `/workspace/PolicyStack/lefthook.yml:63-73`, which both
execute `./grade.sh` and cause `grade.sh` to detect `STACK="python"` (lines `26-31`) and
enter the Python case at `grade.sh:96-108`.

4. In the Python case, `grade.sh` runs several checks against `src` at lines `99-101` and
`105-106`: `ruff check src`, `ruff format --check src`, `mypy src`, `pytest --cov=src
--cov-report=term-missing --cov-fail-under=85`, and `bandit -r src`; because `src/` does
not exist in this repo, these commands fail due to bad paths, causing multiple checks to
be marked as failed and pushing the overall grade percentage (computed at
`grade.sh:127-132`) below the configured pass threshold even when the actual Python
sources and tests are healthy.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** grade.sh
**Line:** 99:106
**Comment:**
	*Incorrect Variable Usage: These Python checks are hardcoded to `src`, but this repo has no top-level `src/` directory, so lint/typecheck/security/coverage checks fail due to wrong paths rather than real quality issues. Point checks at the real Python locations used by the project.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

run_check "audit" "pip-audit" 1 true
Comment on lines +97 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Architect Review — HIGH

The Python grading path assumes a pip-installed .[dev] extra and a top-level src/ package, but this repo's Python workflow is uv/Taskfile-based with no dev extra and no src/ directory, so task grade (and the pre-push hook that runs it) will consistently fail for environment/layout reasons rather than actual project quality.

Suggestion: Align the Python checks with this repository's existing contract (uv/Taskfile commands, real package paths, and declared extras), or make Python stack detection adaptive/opt-out for this repo so pre-push only blocks on checks that match the current Python toolchain and layout.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** grade.sh
**Line:** 97:107
**Comment:**
	*HIGH: The Python grading path assumes a pip-installed `.[dev]` extra and a top-level `src/` package, but this repo's Python workflow is uv/Taskfile-based with no `dev` extra and no `src/` directory, so `task grade` (and the pre-push hook that runs it) will consistently fail for environment/layout reasons rather than actual project quality.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

;;
go)
run_check "build" "go build ./..." 2
run_check "test-unit" "go test ./..." 3
run_check "fmt" "test -z \"\$(gofmt -l .)\"" 2
run_check "vet" "go vet ./..." 2
run_check "lint" "golangci-lint run" 2
run_check "test-race" "go test -race ./..." 2 true
run_check "test-fuzz" "go test -fuzz=. ./..." 1 true
run_check "test-bench" "go test -bench=. ./..." 1 true
run_check "coverage" "go test -coverprofile=coverage.out -covermode=atomic ./... && go tool cover -func=coverage.out | grep total | awk '{print \$3}' | sed 's/%//' | awk '{exit(\$1 < 85 ? 1 : 0)}'" 2 true
run_check "audit" "govulncheck ./..." 1
;;
*)
echo "Unknown stack: $STACK"
exit 1
;;
esac

# Calculate percentage
PCT=$(( SCORE * 100 / MAX ))

# Determine grade
GRADE="F"
if [[ $PCT -ge 95 ]]; then GRADE="A+"; elif [[ $PCT -ge 90 ]]; then GRADE="A"; elif [[ $PCT -ge 85 ]]; then GRADE="B+"; elif [[ $PCT -ge 80 ]]; then GRADE="B"; elif [[ $PCT -ge 70 ]]; then GRADE="C"; elif [[ $PCT -ge 60 ]]; then GRADE="D"; fi

# Output summary
echo ""
echo "========================================"
echo " SCORE: $SCORE / $MAX ($PCT%)"
echo " GRADE: $GRADE"
echo "========================================"

# JSON output
if [[ "$JSON" == true ]]; then
cat > "$REPORT_DIR/grade.json" <<EOF
{
"project": "$(basename "$(pwd)")",
"stack": "$STACK",
"mode": "$([[ $FAST == true ]] && echo fast || echo full)",
"score": $SCORE,
"max": $MAX,
"percentage": $PCT,
"grade": "$GRADE",
"checks": [
$(IFS=,; echo "${CHECKS[*]}")
],
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
echo "JSON report: $REPORT_DIR/grade.json"
fi

# HTML output
if [[ "$HTML" == true ]]; then
cat > "$REPORT_DIR/grade.html" <<EOF
<!DOCTYPE html>
<html>
<head><title>Grade Report — $(basename "$(pwd)")</title>
<style>
body{font-family:system-ui,sans-serif;max-width:800px;margin:2rem auto;padding:0 1rem}
h1{border-bottom:2px solid #333}
.score{font-size:3rem;font-weight:bold;color:$([[ $PCT -ge 85 ]] && echo "#2d7" || echo "#d42")}
.grade{font-size:1.5rem}
table{width:100%;border-collapse:collapse;margin:1rem 0}
th,td{padding:0.5rem;text-align:left;border-bottom:1px solid #ddd}
th{background:#f5f5f5}
.pass{color:#2d7}.fail{color:#d42}.skip{color:#888}
</style>
</head>
<body>
<h1>Grade Report — $(basename "$(pwd)")</h1>
<p class="score">$PCT% <span class="grade">($GRADE)</span></p>
<p>Stack: $STACK | Mode: $([[ $FAST == true ]] && echo fast || echo full)</p>
<table>
<tr><th>Check</th><th>Status</th><th>Score</th></tr>
EOF
for check in "${CHECKS[@]}"; do
name=$(echo "$check" | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
status=$(echo "$check" | grep -o '"status":"[^"]*"' | cut -d'"' -f4)
score=$(echo "$check" | grep -o '"score":[0-9]*' | cut -d':' -f2)
max=$(echo "$check" | grep -o '"max":[0-9]*' | cut -d':' -f2)
echo "<tr><td>$name</td><td class=\"$status\">$status</td><td>$score/$max</td></tr>" >> "$REPORT_DIR/grade.html"
done
echo "</table><p>Generated: $(date -u)</p></body></html>" >> "$REPORT_DIR/grade.html"
echo "HTML report: $REPORT_DIR/grade.html"
fi

# Exit code
if [[ $PCT -lt 85 ]]; then exit 1; fi
exit 0
Loading
Loading