Skip to content
Merged
66 changes: 66 additions & 0 deletions .agents/skills/gc-watchdog/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
name: gc-watchdog
description: Use when setting up a periodic Gas City health monitor. Trigger when the user asks for a watchdog, periodic status check, or monitoring loop for gc status. Covers the check cycle, exit conditions, and reporting format.
---

# GC Watchdog

Periodic Gas City health monitor. Checks `gc status` and `bd ready` on an
interval, reports issues, and exits when there is no more open work.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

## When to use

- User asks to "watch" or "monitor" Gas City while work is in progress
- User wants periodic status reports during a long-running wave
- User wants to know when the city goes idle (all work done)

## Exit conditions

The watchdog stops when ALL of these are true:

1. No open real tasks: `bd list --status=open` returns no issues (excluding
ephemeral wisp/nudge beads)
2. No in-progress workflows: no beads with `gc.kind: workflow` in
`in_progress` state
3. Mayor is awake and not suspended (clean shutdown, not a crash)

If the mayor is down or the city is suspended, the watchdog reports the
error and keeps running (does NOT exit on failure — only on success).

## Check cycle

Each tick:

1. `gc status` — extract mayor status, sessions, suspended, controller
2. `bd list --status=open` — count real (non-wisp, non-nudge) open issues
3. `bd list --status=in_progress` — count real in-progress issues
4. Report one line
5. If no open AND no in-progress real issues → report "idle" and exit 0

## Reporting format

```
[HH:MM:SS] ✓ mayor:<status> | open:<N> in_progress:<M> | <sessions line>
[HH:MM:SS] ⚠ <issues> | open:<N> in_progress:<M> | <sessions line>
[HH:MM:SS] IDLE — no open work, no in-progress work. Watchdog exiting.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
```

## Usage

### As a skill (agent-driven)

The agent reads this SKILL.md, runs the check script, and reports back.
Each tick is one subagent invocation: wait 60s, check, report.

### As a standalone script

```bash
bash .agents/skills/gc-watchdog/watchdog.sh [interval_seconds]
```

Default interval: 60 seconds. Runs until idle or interrupted.

## Files

- `watchdog.sh` — standalone shell script implementing the check cycle
- `SKILL.md` — this file (documentation for agents)
112 changes: 112 additions & 0 deletions .agents/skills/gc-watchdog/watchdog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/bin/bash
# gc-watchdog — periodic Gas City health monitor with exit condition.
#
# Checks gc status + bd ready on an interval. Reports issues. Exits when
# there is no more open real work (non-wisp, non-nudge) and no in-progress
# real work.
#
# Usage: bash watchdog.sh [interval_seconds]
# Default interval: 60 seconds.

# NOTE: intentionally NOT using set -e. gc status and bd list can fail
# transiently (session snapshot timeouts, store locks). We handle errors
# per-command and continue the loop.
set -uo pipefail
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved

INTERVAL="${1:-60}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
# Validate INTERVAL is a positive integer to prevent injection.
if ! [[ "$INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
echo "ERROR: interval must be a positive integer, got: $INTERVAL" >&2
exit 2
fi
ITER=0
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved

# Filter out ephemeral wisp/nudge beads from bd output.
# Real issues have IDs like sv-XXXX (4+ chars after sv-).
# Wisp/nudge beads have IDs like sv-wisp-XXXX or sv-nudge-XXXX.
# Status symbols: ○ = open, ◐ = in_progress, ● = blocked, ✓ = closed
# Returns -1 on bd failure (distinct from a legitimate 0 count) so the
# caller can distinguish "no work" from "lookup failed".
count_real_issues() {
local status="$1"
local count
if ! count=$(
bd list --status="$status" 2>/dev/null \
| grep -E '^\s*[○◐●] sv-[a-z0-9]{4,}($| )' \
| grep -vE 'sv-(wisp|nudge)' \
| wc -l 2>/dev/null
); then
printf -- '-1\n'
return 1
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved
fi
printf '%s\n' "$count"
}

while true; do
ITER=$((ITER + 1))
TS=$(date '+%H:%M:%S')

# 1. gc status
STATUS=$(timeout 15 gc status 2>/dev/null || true)

if [ -z "$STATUS" ]; then
echo "[$TS] #$ITER ⚠ gc status timeout/error"
sleep "$INTERVAL"
continue
fi
Comment thread
qodo-code-review[bot] marked this conversation as resolved.
Resolved

MAYOR=$(echo "$STATUS" | grep "harness.mayor" | awk '{print $2}')
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved
SESSIONS=$(echo "$STATUS" | grep "Sessions:" | head -1 | sed 's/^ *//')
SUSPENDED=$(echo "$STATUS" | grep "Suspended:" | awk '{print $2}')
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved
CONTROLLER=$(echo "$STATUS" | grep "Controller:" | grep -o "supervisor-managed\|stopped\|error" | head -1 || true)

# Handle transient lookup errors (mayor shows "lookup" instead of "awake")
if echo "$MAYOR" | grep -q "lookup"; then
MAYOR="lookup-error"
fi

# 2. bd work counts
OPEN_COUNT=$(count_real_issues "open")
INPROG_COUNT=$(count_real_issues "in_progress")

# 3. Check for issues
ISSUES=""
[ -z "$MAYOR" ] && ISSUES="$ISSUES MAYOR_MISSING"
if [ -n "$MAYOR" ] && [ "$MAYOR" != "awake" ] && [ "$MAYOR" != "running" ] && [ "$MAYOR" != "active" ] && [ "$MAYOR" != "lookup-error" ]; then
ISSUES="$ISSUES MAYOR($MAYOR)"
fi
# lookup-error is a transient issue, not a hard failure — report as ⚠ but don't treat as fatal
if [ "$MAYOR" = "lookup-error" ]; then
ISSUES="$ISSUES MAYOR_LOOKUP_TIMEOUT"
fi
# Treat missing status fields as unknown, not healthy defaults
if [ -z "${SUSPENDED:-}" ]; then
ISSUES="$ISSUES SUSPENDED_MISSING"
elif [ "$SUSPENDED" != "no" ]; then
ISSUES="$ISSUES SUSPENDED"
fi
if [ -z "${CONTROLLER:-}" ]; then
ISSUES="$ISSUES CONTROLLER_MISSING"
elif [ "$CONTROLLER" != "supervisor-managed" ]; then
ISSUES="$ISSUES CONTROLLER($CONTROLLER)"
fi

# 4. Report
if [ -n "$ISSUES" ]; then
echo "[$TS] #$ITER ⚠$ISSUES | open:$OPEN_COUNT in_progress:$INPROG_COUNT | ${SESSIONS:-no-sessions}"
else
echo "[$TS] #$ITER ✓ mayor:$MAYOR | open:$OPEN_COUNT in_progress:$INPROG_COUNT | ${SESSIONS:-no-sessions}"
fi

# 5. Exit condition: no open AND no in-progress real work, mayor healthy.
# Note: lookup-error is transient, don't block exit on it — only exit on
# clean idle (no real issues, no hard failures). MAYOR_LOOKUP_TIMEOUT is
# a soft signal, so strip it from ISSUES before checking the exit condition.
HARD_ISSUES="${ISSUES// MAYOR_LOOKUP_TIMEOUT/}"
if [ "$OPEN_COUNT" -eq 0 ] && [ "$INPROG_COUNT" -eq 0 ] && [ -z "$HARD_ISSUES" ]; then
Comment thread
ThePlenkov marked this conversation as resolved.
Resolved
echo "[$TS] #$ITER IDLE — no open work, no in-progress work. Watchdog exiting."
exit 0
fi

sleep "$INTERVAL"
done
20 changes: 20 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: "Sverka CodeQL Config"

# Exclude non-source paths from CodeQL analysis
paths:
- packages
paths-ignore:
- packages/*/dist/**
- "**/node_modules/**"
- "**/__tests__/**"
- "**/*.test.ts"
- pack/**
- .agents/**
- .gc/**
- website/**
- engdocs/**
- specs/**

# Query suite: security-extended (set in workflow)
queries:
- uses: security-extended
83 changes: 83 additions & 0 deletions REVIEW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Review Policy

## Two-Axis Review

Every change is reviewed along two independent axes:

1. **Standards** — does the code follow the repo's documented coding
standards (AGENTS.md) plus a Fowler smell baseline?
2. **Spec** — does the code faithfully implement the originating spec
in `specs/`?

Both axes must pass. A change that is clean code but implements the wrong
thing is rejected. A change that implements the right thing with messy code
is rejected.

## Verification Bar

The reviewer runs all checks themselves. No trusting builder claims.

```bash
bun run test # vitest via nx (NOT `bun test` — that runs Bun's runner)
bun run typecheck # tsc via nx
bun run lint # eslint via nx
bun run build # tsdown via nx
```

Run fresh with `--skip-nx-cache` to avoid cached results.

## Finding Classification

| Class | Meaning | Action |
| ------- | ------------------------------ | --------------- |
| BLOCKING | Spec violation or broken gate | Must fix |
| NIT | Non-blocking style or edge case | Note, don't block |
| DECLINE | Reviewer disagrees with suggestion | Explain why |

## Minimalism Audit

If the builder wrote more code than the spec requires, that's a rejection
for over-engineering. Less code = fewer bugs. The reviewer audits for:

- Unnecessary abstractions
- Speculative API (exports not used by spec)
- Dead code
- Premature generalization

## Spec Compliance

Every interface, every type, every error code in the spec must be present
in the implementation. No "close enough." No "it's basically the same."

Exports must match spec 1:1. No extra exports beyond what the spec defines
(testability seams excepted — note them as NITs).

## Error Handling

- Custom error classes must use `override` on `cause` (noImplicitOverride)
- No `any` types — use `unknown` and narrow
- Error codes as string unions, not enums

## Scaffolding Checks

- `package.json`: dist outputs as `.mjs`/`.d.mts` (not `.js`/`.d.ts`)
- `project.json`: lint target without `--ext .ts` (ESLint 9 flat config)
- `project.json`: test target with `--passWithNoTests`

## Commit Hygiene

Before committing a wave, verify:

- `git status --short` — every impl + test file is at least staged
- No untracked impl files (recurrence of untracked-test-helpers drill finding)
- Exclude: `city.toml`, `agents/`, `.devin/`, `.gc/`, `.beads/`, `.evidence/`,
`.opencode/`, `formulas/` (process improvement files)
- Stage only: `packages/<package>/**`, `specs/NN-<name>/`, `engdocs/`, `bun.lock`

## Process

1. Reviewer runs all gates fresh (not cached, not trusted from builder)
2. Reviewer reads the diff and spec
3. Reviewer classifies findings
4. APPROVE or REJECT with specific, actionable feedback
5. On REJECT: builder fixes, reviewer re-reviews
57 changes: 57 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Security Policy

## Reporting Vulnerabilities

Report security vulnerabilities privately. Do NOT open a public GitHub issue.

- Email: security@sverka.dev
- Response time: 48 hours
- Disclosure: coordinated, after fix is released

## Secrets

- NEVER commit secrets, API keys, tokens, or passwords to the repository
- Use environment variables or secret managers
- If a secret is accidentally committed: rotate it immediately, then force-push
the branch to remove it from history
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
- Reviewer must check for hardcoded secrets in every PR

## Dependencies

- Prefer dependencies published at least 7 days ago (supply chain safety)
- Avoid floating ranges (`latest`, `*`, unbounded `>=`) that auto-resolve to
brand-new releases
- Run `bun audit` periodically
- Pin lockfile (`bun.lock`) — do not commit without it

## Code Security

- No `any` types (use `unknown` and narrow) — prevents type confusion bugs
- Validate all external input before use
- No `eval()`, no `new Function()`, no dynamic code execution
- No `child_process.exec` with user input — use `execFile` with arg arrays
- Sanitize file paths — no path traversal via `..` in user input
- Use `spawnSync` with timeout for subprocess calls (prevent hangs)

## CI/CD Security

- Never modify repository security policies or compliance controls to work
around CI failures
- Never disable branch protection, even temporarily
- Escalate CI/auth failures to the user instead of working around them

## Agent Security

- Agents must not expose or log secrets
- Agents must not commit secrets
- Agents must not modify security policies
- `dangerous` permission mode is for local dev only, never for CI

## Runtime Security

Sverka executes commands in Docker containers and host processes:

- Docker executor: verify image digests, no untrusted images
- Host executor: validate commands before execution, no shell injection
- Runtime args: sanitize all user-provided paths and arguments
- Credentials: pass via env vars, never log them
Loading
Loading