| name | titan-gate |
| description | Validate staged changes — codegraph checks + project lint/build/test, auto-rollback on failure, pass/fail commit gate (Titan Paradigm Phase 4) |
| argument-hint | <--force to skip warnings> |
| allowed-tools | Bash, Read, Write, Edit, Grep |
Titan GATE — Change Validation & State Machine
You are running the GATE phase (State Machine) of the Titan Paradigm.
Your goal: validate staged changes against codegraph quality checks AND the project's own lint/build/test. Produce a clear PASS/WARN/FAIL verdict. Auto-rollback on failure.
Context budget: Lightweight — only checks staged changes. Should complete quickly.
Force mode: If $ARGUMENTS contains --force, warnings are downgraded (failures still block).
Step 0 — Pre-flight
-
Worktree check:
git rev-parse --show-toplevel && git worktree list
If not in a worktree, stop: "Run /worktree first."
-
Staged changes?
git diff --cached --name-only
If nothing staged, stop: "Nothing staged. Use git add first."
-
Load state (optional). Read .codegraph/titan/titan-state.json if it exists — use for thresholds, baseline comparison, and sync alignment. If missing or corrupt, proceed with defaults.
Step 1 — Structural validation (codegraph)
Run the full change validation predicates in one call:
codegraph check --staged --cycles --blast-radius 30 --boundaries -T --json
This checks: manifesto rules, new cycle introduction, blast radius threshold, and architecture boundary violations. Exit code 0 = pass, 1 = fail.
The blast-radius predicate is call-graph-shape-aware: a touched function's absolute transitive-caller count only counts toward the threshold if the diff actually changed that function's own declaration line or the set of call targets referenced in its body. A function whose body changed without touching a call expression or its own signature (e.g. an inline literal replaced by a named constant) is exempt even with a high absolute caller count — that fan-in is pre-existing risk already accepted by the codebase, not risk this diff introduced. This is the authoritative blast-radius pass/fail — see Step 8.
Also run detailed impact analysis:
codegraph diff-impact --staged -T --json
Extract: changed functions (count + names), direct callers affected, transitive blast radius, historically coupled files.
Step 2 — Cycle check
codegraph cycles --json
Compare against RECON baseline (if titan-state.json exists):
- New cycles? → FAIL
- Cycles resolved? → Note as positive
Step 3 — Complexity delta
For each changed file (from diff-impact):
codegraph complexity --file <changed-file> --health -T --json
Check all metrics against thresholds:
cognitive > 30 → FAIL
halstead.bugs > 1.0 → FAIL (estimated defect)
mi < 20 → FAIL
- Function moved from PASS → FAIL on any metric? → FAIL
- Function improved but still above threshold? → WARN
Step 4 — Lint, build, and test
Detect project tools from package.json:
node -e "const p=require('./package.json');console.log(JSON.stringify(Object.keys(p.scripts||{})))"
Run in order — stop on first failure:
lintCmd=$(node -e "const p=require('./package.json');const s=p.scripts||{};if(!s.lint){console.log('NO_LINT_SCRIPT');process.exit(0);}const fs=require('fs');const runner=fs.existsSync('yarn.lock')?'yarn':fs.existsSync('pnpm-lock.yaml')?'pnpm':fs.existsSync('bun.lockb')?'bun':'npm';console.log(runner+' run lint');")
if [ "$lintCmd" != "NO_LINT_SCRIPT" ]; then
$lintCmd 2>&1 || echo "LINT_FAILED"
fi
buildCmd=$(node -e "const p=require('./package.json');const s=p.scripts||{};if(!s.build){console.log('NO_BUILD_SCRIPT');process.exit(0);}const fs=require('fs');const runner=fs.existsSync('yarn.lock')?'yarn':fs.existsSync('pnpm-lock.yaml')?'pnpm':fs.existsSync('bun.lockb')?'bun':'npm';console.log(runner+' run build');")
if [ "$buildCmd" != "NO_BUILD_SCRIPT" ]; then
$buildCmd 2>&1 || echo "BUILD_FAILED"
fi
testCmd=$(node -e "const p=require('./package.json');const s=p.scripts||{};const script=s.test?'test':s['test:ci']?'test:ci':null;if(!script){console.log('NO_TEST_SCRIPT');process.exit(0);}const fs=require('fs');const runner=fs.existsSync('yarn.lock')?'yarn':fs.existsSync('pnpm-lock.yaml')?'pnpm':fs.existsSync('bun.lockb')?'bun':'npm';console.log(runner+(script==='test'?' test':' run '+script));")
if [ "$testCmd" != "NO_TEST_SCRIPT" ]; then
$testCmd 2>&1 || echo "TEST_FAILED"
fi
If any fail → overall verdict is FAIL → proceed to auto-rollback.
Step 5 — Semantic assertions (API compatibility)
Verify that code changes don't silently break callers by changing public contracts. This goes beyond structural checks — it catches signature changes, removed exports, and new forbidden dependencies.
5a. Export signature stability
Get the list of changed files from diff-impact (Step 1):
codegraph exports <changed-file> -T --json
For each changed file from diff-impact (Step 1):
5b. Import resolution integrity
From the diff-impact results already collected in Step 1, extract any edges where the target symbol or file no longer exists (i.e., the import points to a removed or renamed symbol).
For each such broken edge where the importing file is NOT part of this commit's staged changes → FAIL: "Change broke import resolution for : "
Note: codegraph check does not include import resolution predicates — its checks cover cycles, blast-radius, boundaries, and manifesto rules. Import resolution runs during codegraph build. This step relies on diff-impact's edge data to detect broken imports indirectly by identifying edges that reference removed symbols.
5c. Dependency direction assertions (codegraph boundary rules)
Check the Step 1 codegraph check --staged --boundaries results for boundary violations (already collected — do not re-run). This covers .codegraphrc.json onion-architecture rules and any custom boundary predicates.
Store flagged edges in step5cViolations — a list of { source, target } pairs. For each boundary violation reported by codegraph check:
- New dependency that violates a configured boundary rule → FAIL: "New upward dependency:
<source> → <target> violates layer boundary"
- Add
{ source, target } to step5cViolations
Additionally, from the diff-impact results already collected in Step 1, extract any new edges (imports that didn't exist before):
- New dependency on a module flagged in sync.json as "to be removed" or "to be split" → WARN: "New dependency on
<module> which is scheduled for decomposition"
Note: Step 5c relies exclusively on codegraph check --boundaries results. Domain-direction checks against GLOBAL_ARCH.md are handled by Step 5.5 A2 — do not duplicate them here. Pass step5cViolations to A2 so it can skip edges already flagged here.
5d. Re-export chain validation
If the change modifies an index/barrel file (e.g., index.js, mod.rs):
Capture the pre-change export list from the committed version (write the temp path to a sidecar file so it persists across Bash invocations):
BARREL_EXT="${barrel_file##*.}"
BARREL_TMP=$(mktemp "/tmp/titan-barrel-XXXXXX.${BARREL_EXT}")
echo "$BARREL_TMP" > .codegraph/titan/.barrel-tmp
git show HEAD:<barrel-file> > "$BARREL_TMP"
codegraph exports "$BARREL_TMP" -T --json
Then capture the current (staged) export list:
codegraph exports <barrel-file> -T --json
Compare export count before and after. If exports were accidentally dropped (count decreased and the removed exports have callers) → FAIL: "Barrel file <barrel-file> dropped exports that have active callers: . Use codegraph exports <barrel-file> -T to review."
Clean up the temp file (recover path from sidecar). This MUST run even if Step 5d produced a FAIL verdict — run it before proceeding to Step 9:
BARREL_TMP=$(cat .codegraph/titan/.barrel-tmp 2>/dev/null)
if [ -n "$BARREL_TMP" ]; then rm -f "$BARREL_TMP"; fi
rm -f .codegraph/titan/.barrel-tmp
Step 5.5 — Architectural snapshot comparison
Compare the codebase's architectural properties before and after this change. This catches "technically correct but architecturally wrong" changes — e.g., a valid refactor that puts code in the wrong layer.
A2. Dependency direction between domains (runs unconditionally)
A2 does NOT require arch-snapshot.json — it uses only the Step 1 diff-impact results and GLOBAL_ARCH.md (if available).
If .codegraph/titan/GLOBAL_ARCH.md does not exist (standalone invocation without Titan artifacts), skip A2 — layer boundaries are unknown.
From GLOBAL_ARCH.md, extract the expected dependency direction between domains (e.g., "presentation depends on features, not the reverse").
Check if any new cross-domain dependency violates the expected direction. Use the Step 1 diff-impact results to extract only the edges introduced by the staged changes — do not re-run codegraph deps on the full file (that returns all dependencies including pre-existing ones). For each new edge in the diff-impact output, the source and target file paths are already present in the edge data. Resolve the domain/layer of each endpoint by matching its file path against the domain map in GLOBAL_ARCH.md (e.g., src/presentation/ → presentation layer, src/features/ → features layer). No additional codegraph command is needed — the diff-impact edge output contains the file paths directly.
- New upward dependency (lower layer importing higher layer) introduced in this diff → check if
{ source, target } is already in step5cViolations (passed from Step 5c). If yes, skip (already reported). Otherwise → FAIL
- Pre-existing boundary violations not surfaced by Step 5c's staged-diff results → advisory-only (not gating)
- New lateral dependency within the same layer → OK
Load pre-forge snapshot (A1, A3, A4 — snapshot-dependent)
Read .codegraph/titan/arch-snapshot.json if it exists (created by /titan-run before forge begins). If missing, skip the remaining assertions (A1, A3, A4) in this step — they require the pre-forge baseline. A2 above already ran unconditionally.
Capture current state
Use mktemp -d to create a unique temporary directory that persists across Bash invocations (shell variables like $TITAN_TMP_ID do not survive between separate Bash tool calls):
TITAN_ARCH_DIR=$(mktemp -d /tmp/titan-arch-XXXXXX)
echo "$TITAN_ARCH_DIR" > .codegraph/titan/.arch-tmpdir
codegraph communities -T --json > "$TITAN_ARCH_DIR/current-communities.json" || echo '{"ARCH_CAPTURE_FAILED":"communities"}' > "$TITAN_ARCH_DIR/current-communities.json"
codegraph structure --depth 2 --json > "$TITAN_ARCH_DIR/current-structure.json" || echo '{"ARCH_CAPTURE_FAILED":"structure"}' > "$TITAN_ARCH_DIR/current-structure.json"
codegraph communities --drift -T --json > "$TITAN_ARCH_DIR/current-drift.json" || echo '{"ARCH_CAPTURE_FAILED":"drift"}' > "$TITAN_ARCH_DIR/current-drift.json"
The path is written to .codegraph/titan/.arch-tmpdir so subsequent Bash invocations can recover it via TITAN_ARCH_DIR=$(cat .codegraph/titan/.arch-tmpdir).
Compare
Before comparing: Check each captured file for ARCH_CAPTURE_FAILED. If a file contains this marker, skip the corresponding assertion (A1/A3/A4) and report: "Skipping — codegraph failed during capture."
In a new Bash invocation, recover the temp dir path first:
TITAN_ARCH_DIR=$(cat .codegraph/titan/.arch-tmpdir)
A1. Community stability:
Use the drift output (which uses content-based matching, not raw IDs, to track community movements across runs):
Read .codegraph/titan/arch-snapshot.json → drift (the pre-forge drift baseline) and compare against $TITAN_ARCH_DIR/current-drift.json:
- For each new drift warning in current that was NOT present in the snapshot: if the drifted symbol was NOT touched in the diff → WARN: "Symbol
<name> drifted community as a side effect"
- If > 5 untouched symbols appear in new drift warnings → FAIL: "Significant community restructuring detected — symbols drifted communities. This change may have unintended architectural impact."
A3. Cohesion delta:
Compare directory cohesion scores in arch-snapshot.json → structure (baseline) against $TITAN_ARCH_DIR/current-structure.json (current):
- If any directory's cohesion dropped by > 0.2 → WARN: "Directory
<dir> cohesion dropped from to "
- If a directory went from above 0.5 to below 0.3 → FAIL: "Directory
<dir> became tangled (cohesion → )"
A4. Resolved drift warnings (positive signal):
Compare drift warnings between snapshot and current. A1 already covers new drift warnings — A4 only reports resolved ones:
- If any drift warning that was present in the snapshot is absent from
$TITAN_ARCH_DIR/current-drift.json → note as positive: "Symbol <name> community drift resolved — architecture improved"
Note: A3 and A4 compare the pre-forge baseline against the committed state at gate-run time (the graph DB does not include staged-but-uncommitted changes). They catch cumulative architectural drift across all forge commits made so far, not the individual staged change being validated in this gate run. A1 uses staged-change-aware data (diff-impact) and catches per-change violations. A2 runs unconditionally above (before the snapshot gate) and also uses diff-impact data.
Cleanup (MUST run even on failure or early exit)
This cleanup block MUST execute regardless of the verdict — including FAIL paths and early exits. Run it before proceeding to Step 9 (verdict aggregation), not after.
TITAN_ARCH_DIR=$(cat .codegraph/titan/.arch-tmpdir 2>/dev/null)
if [ -n "$TITAN_ARCH_DIR" ]; then
rm -rf "$TITAN_ARCH_DIR"
fi
rm -f .codegraph/titan/.arch-tmpdir
Verdict integration
Architectural failures are reported as part of the overall gate verdict. They participate in the PASS/WARN/FAIL aggregation like all other checks.
Step 6 — Branch structural diff
codegraph branch-compare main HEAD -T --json
Cumulative structural impact of all changes on this branch (broader than diff-impact --staged). Detect cumulative drift.
Step 7 — Sync plan alignment
If .codegraph/titan/sync.json exists:
- Are changed files part of the current execution phase?
- Are dependencies for these targets already completed?
- Skipping ahead in execution order? → WARN
Advisory — prevents jumping ahead and creating conflicts.
Step 8 — Blast radius check
The FAIL/PASS decision comes from Step 1's codegraph check --staged --blast-radius 30 predicate — do not re-derive it from diff-impact's raw numbers, which are absolute transitive-caller counts and are NOT shape-aware:
- Step 1's
blast-radius predicate failed → FAIL (a touched function's own call graph shape genuinely changed and its transitive callers exceed 30)
- Step 1's
blast-radius predicate passed but a changed function's raw transitiveCallers (from diff-impact) > 15 → WARN (worth a second look even though not gating — this may include functions Step 1 exempted as pre-existing fan-in)
- Historically coupled file NOT staged? → WARN ("consider also updating X")
Step 9 — Verdict and auto-rollback
Aggregate all checks:
| Verdict | Meaning |
|---|
| PASS | Safe to commit |
| WARN | Warnings only — commit at your discretion |
| FAIL | Failures present — auto-rollback triggered |
Auto-rollback on FAIL (cycle/build/test/lint failures)
-
Restore graph to the most recent snapshot:
codegraph snapshot restore titan-batch-<N>
Check titan-state.json → snapshots.lastBatch first; fall back to snapshots.baseline.
-
Unstage changes (preserve in working tree):
git reset HEAD
-
Rebuild graph for current working tree state:
codegraph build
"GATE FAIL: [reason]. Graph restored, changes unstaged but preserved. Fix and re-stage."
For structural and semantic failures (Steps 1 [manifesto/blast-radius/boundary only — not cycles], 3, 5, 5.5, 6-8), do NOT auto-rollback — report the FAIL and let the user decide whether to fix in place or unstage manually.
For Step 2 (new cycle), Step 1 cycle violations (from --cycles flag), and Step 4 (lint/build/test), trigger auto-rollback.
Snapshot cleanup on pipeline completion
When the full Titan pipeline is done (all SYNC phases complete, final GATE passes):
codegraph snapshot delete titan-baseline
codegraph snapshot delete titan-batch-<N>
"All Titan snapshots cleaned up. Codebase is in its final validated state."
Step 10 — Update state machine
Append to .codegraph/titan/gate-log.ndjson:
{
"timestamp": "<ISO 8601>",
"verdict": "PASS|WARN|FAIL",
"stagedFiles": ["file1.js"],
"changedFunctions": 3,
"blastRadius": 12,
"checks": {
"manifesto": "pass|fail",
"cycles": "pass|fail",
"complexity": "pass|warn|fail",
"semanticAssertions": "pass|warn|fail|skipped",
"archSnapshot": "pass|warn|fail|skipped",
"lint": "pass|fail|skipped",
"build": "pass|fail|skipped",
"tests": "pass|fail|skipped",
"syncAlignment": "pass|warn|skipped",
"blastRadius": "pass|warn|fail"
},
"rolledBack": false
}
Update titan-state.json (if exists): increment progress.fixed, update fileAudits for fixed files.
Step 11 — Report to user
PASS:
GATE PASS — safe to commit
Changed: 3 functions across 2 files
Blast radius: 12 transitive callers
Structural: pass | Semantic: pass | Architecture: pass
Lint: pass | Build: pass | Tests: pass
Complexity: all within thresholds (worst: halstead.bugs 0.3)
WARN:
GATE WARN — review before committing
Changed: 5 functions across 3 files
Warnings:
- utils.js historically co-changes with config.js (not staged)
- parseConfig MI improved 18 → 35 but still below 50
- Semantic: new dependency on module scheduled for decomposition
- Architecture: directory src/domain/ cohesion dropped 0.6 → 0.45
FAIL (cycle/test/lint/build failures — rollback triggered):
GATE FAIL — changes unstaged, graph restored
Failures:
- Tests: 2 suites failed
- New cycle: parseConfig → loadConfig → parseConfig
Fix issues, re-stage, re-run /titan-gate
FAIL (structural/semantic failures — no rollback):
GATE FAIL — changes preserved for review — manual unstage if needed
Failures:
- Semantic: removed export `parseConfig` still imported by 3 files
- Architecture: new upward dependency presentation/ → domain/
Staged changes are intact. Fix the issues above, or manually run `git reset HEAD` to unstage.
Re-stage and re-run /titan-gate when ready.
Rules
- Fast execution. Only staged changes, not full codebase.
- Always use
--json and -T.
- Never auto-commit. Verdict only — user decides.
- Auto-rollback is gentle —
git reset HEAD, never git checkout. Work preserved.
- Append to gate-log.ndjson — the audit trail.
- Force mode downgrades WARN → PASS but cannot override FAIL.
- Run the project's own lint/build/test — codegraph checks are necessary but not sufficient.
- Use the correct check flags:
--cycles, --blast-radius <n>, --boundaries.
Self-Improvement
This skill lives at .claude/skills/titan-gate/SKILL.md. Adjust thresholds or rollback behavior after dogfooding.