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
447 changes: 447 additions & 0 deletions .claude/skills/titan-grind/SKILL.md

Large diffs are not rendered by default.

125 changes: 117 additions & 8 deletions .claude/skills/titan-run/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: titan-run
description: Run the full Titan Paradigm pipeline end-to-end by dispatching each phase to sub-agents with fresh context windows. Orchestrates recon → gauntlet → sync → forge automatically.
argument-hint: <path (default: .)> <--skip-recon> <--skip-gauntlet> <--start-from recon|gauntlet|sync|forge> <--gauntlet-batch-size 5> <--yes>
description: Run the full Titan Paradigm pipeline end-to-end by dispatching each phase to sub-agents with fresh context windows. Orchestrates recon → gauntlet → sync → forge → grind automatically.
argument-hint: <path (default: .)> <--skip-recon> <--skip-gauntlet> <--start-from recon|gauntlet|sync|forge|grind> <--gauntlet-batch-size 5> <--yes>
allowed-tools: Agent, Read, Bash, Glob, Write, Edit
---

Expand All @@ -16,7 +16,7 @@ You are the **orchestrator** for the full Titan Paradigm pipeline. Your job is t
- `<path>` → target path (passed to recon)
- `--skip-recon` → skip recon (assumes artifacts exist)
- `--skip-gauntlet` → skip gauntlet (assumes artifacts exist)
- `--start-from <phase>` → jump to phase: `recon`, `gauntlet`, `sync`, `forge`
- `--start-from <phase>` → jump to phase: `recon`, `gauntlet`, `sync`, `forge`, `grind`
- `--gauntlet-batch-size <N>` → batch size for gauntlet (default: 5)
- `--yes` → skip all confirmation prompts in the orchestrator (pre-pipeline, forge checkpoint, and resume prompts) and in forge (per-phase confirmation)

Expand Down Expand Up @@ -62,11 +62,11 @@ You are the **orchestrator** for the full Titan Paradigm pipeline. Your job is t
```bash
npm install -g @optave/codegraph@latest
```
Log the installed version (skip if codegraph is not available):
Log the installed version:
```bash
codegraph --version || true
codegraph --version
```
If the install fails or `codegraph` is not found, warn the user but continue — the sub-agents may still work if a project-local version is available.
If the install fails, warn the user but continue with whichever version is currently available.

5. **Sync with main** (once, before any sub-agent runs):
```bash
Expand All @@ -81,9 +81,10 @@ You are the **orchestrator** for the full Titan Paradigm pipeline. Your job is t
Starting from: <phase>
Gauntlet batch size: <N>

Phases: recon → gauntlet (loop) → sync → [PAUSE] → forge (loop)
Phases: recon → gauntlet (loop) → sync → [PAUSE] → forge (loop) → grind (loop) → close
Each phase runs in a sub-agent with a fresh context window.
Forge requires explicit confirmation (analysis phases are safe to automate).
Grind runs after forge to adopt extracted helpers into consumers.
```

Start immediately — do NOT ask for confirmation before analysis phases. The user invoked `/titan-run`; that is the confirmation. Analysis phases (recon, gauntlet, sync) are read-only and safe to automate. The forge checkpoint (Step 3.5b) still applies unless `--yes` is set.
Expand Down Expand Up @@ -141,6 +142,7 @@ For each phase BEFORE `startPhase`, run the corresponding V-checks:
| `recon` | V1 structural fields only (domains, batches, priorityQueue, stats — skip `currentPhase == "recon"` check since later phases advance it), V2 (GLOBAL_ARCH.md), V3 (snapshot exists — WARN if missing), V4 (cross-check counts) |
| `gauntlet` | V5 (coverage ≥ 50%), V6 (entry completeness sample), V7 (summary consistency); also run NDJSON integrity check (2c) |
| `sync` | V8 (sync.json structure), V9 (targets trace to gauntlet), V10 (dependency order) |
| `forge` | V14 (final state consistency), V15 (gate log consistency); execution block must exist in titan-state.json |

If ANY required artifact is **missing** → stop: "Cannot start from `<phase>` — `<artifact>` is missing. Run the full pipeline or start from an earlier phase."

Expand Down Expand Up @@ -632,6 +634,113 @@ Record `phaseTimestamps.forge.completedAt`.

---

## Step 4.5 — GRIND (loop)

Grind runs after forge to close the adoption loop. Forge extracts helpers; grind wires them into consumers and removes dead code. Without grind, the dead symbol count inflates with every forge phase.

**Skip if:** `--start-from` is `close`, or `titan-state.json → grind.completedPhases` already covers all forge phases.

### 4.5a. Pre-loop check

Record `phaseTimestamps.grind.startedAt` (only if not already set — grind may be resuming).

Read `.codegraph/titan/sync.json` → count total phases in `executionOrder`.
Read `.codegraph/titan/titan-state.json` → check `grind.completedPhases` (may not exist yet if grind hasn't started).

### 4.5b. Grind loop

Set `maxIterations = 20` (safety limit — same as forge).
Set `stallCount = 0`, `maxStalls = 2`.

```
previousGrindPhases = grind.completedPhases (or [])
iteration = 0

while iteration < maxIterations:
iteration += 1

# Run Pre-Agent Gate (G1-G4)

# Determine next forge phase to grind
Read .codegraph/titan/titan-state.json
grindCompleted = grind.completedPhases (or [])
forgePhases = execution.completedPhases (or [])
ungroundPhases = forgePhases.filter(p => !grindCompleted.includes(p))
if len(ungroundPhases) == 0 → break

nextPhase = ungroundPhases[0]

headBefore = $(git rev-parse HEAD)

yesFlag = "--yes" if autoConfirm else ""
Agent → "Run /titan-grind --phase <nextPhase> <yesFlag>.
Read .claude/skills/titan-grind/SKILL.md and follow it exactly.
Skip worktree check and main sync — already handled.

For each dead helper from forge phase <nextPhase>:
1. Classify: adopt / re-export / promote / false-positive / intentionally-private / remove
2. For adopt/re-export/promote: wire consumers, stage, run /titan-gate, commit
3. For remove: delete, stage, run /titan-gate, commit
4. Gate on dead-symbol delta at phase end"

# Post-agent checks
headAfter = $(git rev-parse HEAD)

Read .codegraph/titan/titan-state.json
newGrindPhases = grind.completedPhases (or [])

if newGrindPhases == previousGrindPhases:
stallCount += 1
Print: "WARNING: Grind iteration <iteration> made no progress (stall <stallCount>/<maxStalls>)"
if stallCount >= maxStalls:
Stop: "Grind stalled on phase <nextPhase>. Check titan-state.json → grind for details."
else:
stallCount = 0

previousGrindPhases = newGrindPhases

# V16. Commit audit
if headAfter != headBefore:
git log --oneline <headBefore>..<headAfter>
commitCount = number of commits
Print: "Grind phase <nextPhase>: <commitCount> adoption commits"
else:
Print: "Grind phase <nextPhase>: no adoptions needed (forge wired everything correctly)"

# V17. Test suite still green (same as forge V13)
if headAfter != headBefore:
testCmd = <same detection as forge V13>
if testCmd != "NO_TEST_SCRIPT":
Run: <testCmd> 2>&1
if tests fail:
Print: "CRITICAL: Test suite fails after grind phase <nextPhase>. Stopping pipeline."
Print: "Commits from this phase: git log --oneline <headBefore>..<headAfter>"
Stop.
```

### 4.5c. Post-loop validation

**V18. Dead-symbol delta:**
Read `grind.deadSymbolBaseline` and `grind.deadSymbolCurrent` from `titan-state.json`.
- If delta > 10: **WARN** "Grind could not fully adopt forge's helpers. <delta> new dead symbols remain."
- Otherwise: Print summary.

**V19. Grind coverage:**
- Count forge phases processed vs total forge phases
- If < 100%: **WARN** with details

Print grind summary:
```
GRIND complete.
Dead symbols: <baseline> → <current> (delta: <+/-N>)
Adoptions: <N> helpers wired, <N> removed, <N> false positives logged
Phases ground: <N>/<M>
```

Record `phaseTimestamps.grind.completedAt`.

---

## Step 5 — CLOSE (report + PRs)

After forge completes, dispatch `/titan-close` to produce the final report with before/after metrics and split commits into focused PRs.
Expand Down Expand Up @@ -676,7 +785,7 @@ Record `phaseTimestamps.close.completedAt`.

- **You are the orchestrator, not the executor.** Never run codegraph commands, edit source files, or make commits yourself. Only spawn sub-agents and read state files. Exceptions (pure validation/snapshot, no code changes): the post-forge test run (V13), NDJSON integrity checks, the V3 baseline snapshot check (`codegraph snapshot list`), and the pre-forge architectural snapshot capture (Step 3.5a) are run directly by the orchestrator.
- **Run the Pre-Agent Gate (G1-G4) before EVERY sub-agent.** No exceptions.
- **One sub-agent at a time.** Phases are sequential — recon before gauntlet, gauntlet before sync, sync before forge.
- **One sub-agent at a time.** Phases are sequential — recon before gauntlet, gauntlet before sync, sync before forge, forge before grind, grind before close.
- **Fresh context per sub-agent.** This is the whole point — each sub-agent gets a clean context window.
- **Read AND validate state files after every sub-agent.** Trust the on-disk state, not the sub-agent's text output — but verify the state is structurally sound.
- **Back up state before every sub-agent.** The `.bak` file is your safety net against mid-write crashes.
Expand Down
8 changes: 4 additions & 4 deletions crates/codegraph-core/src/extractors/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,11 @@ fn match_c_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: u
});
}
"field_expression" => {
let name = fn_node.child_by_field_name("field")
.map(|n| node_text(&n, source).to_string())
let name = named_child_text(&fn_node, "field", source)
.map(|s| s.to_string())
.unwrap_or_else(|| node_text(&fn_node, source).to_string());
let receiver = fn_node.child_by_field_name("argument")
.map(|n| node_text(&n, source).to_string());
let receiver = named_child_text(&fn_node, "argument", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name,
line: start_line(node),
Expand Down
25 changes: 5 additions & 20 deletions crates/codegraph-core/src/extractors/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,6 @@ fn extract_cpp_enum_constants(node: &Node, source: &[u8]) -> Vec<Definition> {
constants
}

fn find_cpp_parent_class<'a>(node: &Node<'a>, source: &[u8]) -> Option<String> {
let mut current = node.parent();
while let Some(parent) = current {
match parent.kind() {
"class_specifier" | "struct_specifier" => {
return parent.child_by_field_name("name")
.map(|n| node_text(&n, source).to_string());
}
_ => {}
}
current = parent.parent();
}
None
}

fn extract_cpp_base_classes(node: &Node, source: &[u8], class_name: &str, symbols: &mut FileSymbols) {
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
Expand Down Expand Up @@ -214,7 +199,7 @@ fn extract_cpp_base_classes(node: &Node, source: &[u8], class_name: &str, symbol

fn handle_cpp_function_definition(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
if let Some(name) = extract_cpp_function_name(node, source) {
let parent_class = find_cpp_parent_class(node, source);
let parent_class = find_enclosing_type_name(node, &["class_specifier", "struct_specifier"], source);
let full_name = match &parent_class {
Some(cls) => format!("{}.{}", cls, name),
None => name,
Expand Down Expand Up @@ -360,11 +345,11 @@ fn handle_cpp_call_expression(node: &Node, source: &[u8], symbols: &mut FileSymb
});
}
"field_expression" => {
let name = fn_node.child_by_field_name("field")
.map(|n| node_text(&n, source).to_string())
let name = named_child_text(&fn_node, "field", source)
.map(|s| s.to_string())
.unwrap_or_else(|| node_text(&fn_node, source).to_string());
let receiver = fn_node.child_by_field_name("argument")
.map(|n| node_text(&n, source).to_string());
let receiver = named_child_text(&fn_node, "argument", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name,
line: start_line(node),
Expand Down
4 changes: 2 additions & 2 deletions crates/codegraph-core/src/extractors/csharp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ fn handle_invocation_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols)
}
"member_access_expression" => {
if let Some(name) = fn_node.child_by_field_name("name") {
let receiver = fn_node.child_by_field_name("expression")
.map(|expr| node_text(&expr, source).to_string());
let receiver = named_child_text(&fn_node, "expression", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name: node_text(&name, source).to_string(),
line: start_line(node),
Expand Down
4 changes: 2 additions & 2 deletions crates/codegraph-core/src/extractors/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ fn handle_call_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
}
"selector_expression" => {
if let Some(field) = fn_node.child_by_field_name("field") {
let receiver = fn_node.child_by_field_name("operand")
.map(|op| node_text(&op, source).to_string());
let receiver = named_child_text(&fn_node, "operand", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name: node_text(&field, source).to_string(),
line: start_line(node),
Expand Down
13 changes: 6 additions & 7 deletions crates/codegraph-core/src/extractors/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ pub fn find_enclosing_type_name(node: &Node, kinds: &[&str], source: &[u8]) -> O
let mut current = node.parent();
while let Some(parent) = current {
if kinds.contains(&parent.kind()) {
return parent
.child_by_field_name("name")
.map(|n| node_text(&n, source).to_string());
return named_child_text(&parent, "name", source)
.map(|s| s.to_string());
}
current = parent.parent();
}
Expand Down Expand Up @@ -541,8 +540,8 @@ fn walk_ast_nodes_with_config_depth(
fn extract_constructor_name(node: &Node, source: &[u8]) -> String {
// Try common field names for the constructed type
for field in &["type", "class", "constructor"] {
if let Some(child) = node.child_by_field_name(field) {
return node_text(&child, source).to_string();
if let Some(text) = named_child_text(node, field, source) {
return text.to_string();
}
}
for i in 0..node.child_count() {
Expand Down Expand Up @@ -599,8 +598,8 @@ fn extract_awaited_name(node: &Node, source: &[u8]) -> String {
/// Extract function name from a call node.
fn extract_call_name(node: &Node, source: &[u8]) -> String {
for field in &["function", "method", "name"] {
if let Some(fn_node) = node.child_by_field_name(field) {
return node_text(&fn_node, source).to_string();
if let Some(text) = named_child_text(node, field, source) {
return text.to_string();
}
}
let text = node_text(node, source);
Expand Down
4 changes: 2 additions & 2 deletions crates/codegraph-core/src/extractors/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,8 @@ fn handle_import_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {

fn handle_method_invocation(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
if let Some(name_node) = node.child_by_field_name("name") {
let receiver = node.child_by_field_name("object")
.map(|obj| node_text(&obj, source).to_string());
let receiver = named_child_text(node, "object", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name: node_text(&name_node, source).to_string(),
line: start_line(node),
Expand Down
14 changes: 7 additions & 7 deletions crates/codegraph-core/src/extractors/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn extract_new_expr_type_name<'a>(node: &Node<'a>, source: &'a [u8]) -> Option<&
match ctor.kind() {
"identifier" => Some(node_text(&ctor, source)),
"member_expression" => {
ctor.child_by_field_name("property").map(|p| node_text(&p, source))
named_child_text(&ctor, "property", source)
}
_ => None,
}
Expand Down Expand Up @@ -905,8 +905,8 @@ fn extract_call_info(fn_node: &Node, call_node: &Node, source: &[u8]) -> Option<
if prop.kind() == "string" || prop.kind() == "string_fragment" {
let method_name = node_text(&prop, source).replace(&['\'', '"'][..], "");
if !method_name.is_empty() {
let receiver = fn_node.child_by_field_name("object")
.map(|obj| node_text(&obj, source).to_string());
let receiver = named_child_text(&fn_node, "object", source)
.map(|s| s.to_string());
return Some(Call {
name: method_name,
line: start_line(call_node),
Expand All @@ -916,8 +916,8 @@ fn extract_call_info(fn_node: &Node, call_node: &Node, source: &[u8]) -> Option<
}
}

let receiver = fn_node.child_by_field_name("object")
.map(|obj| node_text(&obj, source).to_string());
let receiver = named_child_text(&fn_node, "object", source)
.map(|s| s.to_string());
Some(Call {
name: prop_text.to_string(),
line: start_line(call_node),
Expand All @@ -932,8 +932,8 @@ fn extract_call_info(fn_node: &Node, call_node: &Node, source: &[u8]) -> Option<
let method_name = node_text(&index, source)
.replace(&['\'', '"', '`'][..], "");
if !method_name.is_empty() && !method_name.contains('$') {
let receiver = fn_node.child_by_field_name("object")
.map(|obj| node_text(&obj, source).to_string());
let receiver = named_child_text(&fn_node, "object", source)
.map(|s| s.to_string());
return Some(Call {
name: method_name,
line: start_line(call_node),
Expand Down
8 changes: 4 additions & 4 deletions crates/codegraph-core/src/extractors/php.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ fn handle_function_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) {

fn handle_member_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
if let Some(name) = node.child_by_field_name("name") {
let receiver = node.child_by_field_name("object")
.map(|obj| node_text(&obj, source).to_string());
let receiver = named_child_text(node, "object", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name: node_text(&name, source).to_string(),
line: start_line(node),
Expand All @@ -268,8 +268,8 @@ fn handle_member_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) {

fn handle_scoped_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
if let Some(name) = node.child_by_field_name("name") {
let receiver = node.child_by_field_name("scope")
.map(|s| node_text(&s, source).to_string());
let receiver = named_child_text(node, "scope", source)
.map(|s| s.to_string());
symbols.calls.push(Call {
name: node_text(&name, source).to_string(),
line: start_line(node),
Expand Down
Loading
Loading