Skip to content

feat(skill): comprehensive grind rewrite and cross-skill integration#896

Merged
carlos-alm merged 10 commits intomainfrom
feat/adopt-dead-helpers
Apr 9, 2026
Merged

feat(skill): comprehensive grind rewrite and cross-skill integration#896
carlos-alm merged 10 commits intomainfrom
feat/adopt-dead-helpers

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • Rewrites titan-grind with full resilience: state machine, .bak files, NDJSON persistence, snapshot management, codegraph integration (audit/context/fn-impact/diff-impact), diff review (DR1-DR3), drift detection, false positive tracking via issues.ndjson, and phase timestamps
  • Updates titan-close to consume grind artifacts: grind-targets.ndjson in artifact load list, adoption concern type in PR grouping, grind metrics in before/after comparison, GRIND row in Pipeline Timeline, Grind Results section in report template
  • Updates titan-reset to clean up grind artifacts: titan-grind-baseline snapshot deletion and grind-targets.ndjson in artifact listing

Context

Follow-up to #895 (dead helper adoption). The initial grind skill was minimal — this rewrite addresses 25 gaps found by comparing against all other titan skills (forge, gate, close, reset, sync, gauntlet, recon).

Test plan

  • Skills are parseable markdown with valid frontmatter
  • titan-run references grind phase correctly
  • titan-close artifact list includes grind-targets.ndjson
  • titan-reset snapshot cleanup includes titan-grind-baseline

Wire up extracted helpers from Titan runs that existed but were never
consumed, reducing boilerplate and improving error specificity.

- Adopt named_child_text across 27 sites in 11 Rust extractors
- Migrate cpp.rs from hand-rolled find_cpp_parent_class to find_enclosing_type_name
- Add toSymbolRef helper in shared/normalize.ts, adopt at 15 mapping sites
- Wire ParseError in parser.ts for structured PARSE_FAILED error codes
- Wire BoundaryError in boundaries.ts to distinguish config/DB failures from clean results
- Add --modules/--threshold flags to codegraph structure command
- Wire batchQuery in CLI batch command, removing duplicated routing logic
- Route detect-changes pending analysis through unified runAnalyses engine
- manifesto.ts: report 'warn' instead of 'pass' when boundary check throws
- structure.ts: validate --threshold flag rejects non-numeric input
- dependencies.ts: clarify intentional skip of toSymbolRef for callers
Forge extracts helpers but never completes the adoption loop — dead
symbol count inflates with every run. Grind closes the gap by finding
dead helpers from forge, classifying them (adopt/re-export/promote/
false-positive/remove), wiring them into consumers, and gating on a
non-positive dead-symbol delta.

Pipeline is now: recon → gauntlet → sync → forge → grind → close
- Track currentTarget, processedTargets, failedTargets in state for
  mid-run resume after interruption
- Persist grind classifications to grind-targets.ndjson (append-only)
  so re-runs skip already-analyzed targets
- Write titan-state.json after every target, not just at phase end
- Add interrupted-mid-target recovery logic in edge cases
- Use codegraph audit/context/fn-impact/where/query/ast before edits
- Add codegraph diff-impact --staged before commits
- Add codegraph build after edits to keep graph current
- Add --target flag for retrying individual failures
Rewrite titan-grind with full resilience (state machine, .bak files,
NDJSON persistence, snapshot management), codegraph integration
(audit/context/fn-impact/diff-impact), diff review (DR1-DR3), drift
detection, false positive tracking via issues.ndjson, and phase
timestamps.

Update titan-close: grind-targets.ndjson in artifact load list,
adoption concern type in PR grouping, grind metrics in before/after
comparison, GRIND row in Pipeline Timeline, Grind Results section
in report template, grind block in close-summary.json.

Update titan-reset: titan-grind-baseline snapshot deletion and
grind-targets.ndjson in artifact listing.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR is a comprehensive rewrite of titan-grind with a full state machine, NDJSON persistence, snapshot management, codegraph integration, and diff review checks (DR1–DR3). It also wires grind artifacts into titan-close's metrics/report and titan-reset's cleanup, and adds grind pre-flight validation to titan-run --start-from grind. The previously flagged schema mismatch and null guard issues are confirmed fixed in the follow-up commits.

Confidence Score: 5/5

Safe to merge — prior P0/P1 concerns are resolved and the single remaining finding is a P2 documentation gap.

All previously flagged issues (schema mismatch in titan-close grind metrics, null guard in batchQuery) are confirmed fixed. The only new finding is a missing explicit dry-run guard at Step 3 of titan-grind, which is a P2 style issue — the intent is documented in the Rules section, and LLM agents reading the full skill will still respect it. The batch.ts null guard restoration is clean and correct.

.claude/skills/titan-grind/SKILL.md — Step 3 dry-run guard is implicit rather than structural.

Vulnerabilities

No security concerns identified. The skill files are instruction documents executed by an LLM agent; they do not introduce code-level vulnerabilities. The batch.ts change restores a null guard and has no security implications.

Important Files Changed

Filename Overview
.claude/skills/titan-grind/SKILL.md Full rewrite with state machine, NDJSON persistence, snapshot management, and codegraph integration; one minor gap: Step 3 lacks an explicit dry-run guard at its header.
.claude/skills/titan-close/SKILL.md Grind artifact integration aligned: grind-targets.ndjson in artifact list, grind metrics reading corrected to use classification field and titan-state.json grind block, adoption concern type added to grouping strategy.
.claude/skills/titan-reset/SKILL.md Correctly adds titan-grind-baseline snapshot deletion in Step 2 and grind-targets.ndjson to the artifact listing in Step 3.
.claude/skills/titan-run/SKILL.md Adds grind row to Step 0.5 pre-validation table for --start-from grind; grind loop in Step 4.5 and post-loop validation are consistent with titan-grind's state schema.
src/presentation/batch.ts Null guard restored before typeof check in isMulti detection; the fix is correct and prevents TypeError on null first elements.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[titan-run Step 4.5] --> B[Grind Loop]
    B --> C{unground phases?}
    C -- no --> D[Post-loop V18 validation]
    C -- yes --> E[titan-grind --phase N]
    E --> F[Step 0: Pre-flight + state init]
    F --> G[Step 1: Identify forge symbols]
    G --> H[Step 2: Classify dead helpers\nPersist to grind-targets.ndjson]
    H --> I{dry-run?}
    I -- yes --> J[Print plan, stop]
    I -- no --> K[Step 3: Per-target adoption loop]
    K --> L[DR1-DR3 Diff review]
    L -- DIFF FAIL --> M[Rollback staged, add to failedTargets]
    L -- pass --> N[codegraph diff-impact]
    N --> O[Run tests]
    O -- fail --> P[Snapshot restore + git rollback]
    O -- pass --> Q[titan-gate]
    Q -- FAIL --> P
    Q -- PASS --> R[git commit]
    R --> S[Update titan-state.json + .bak]
    S --> K
    M --> K
    P --> K
    K --> T[Step 4: Dead-symbol delta gate]
    T --> U[Step 5: Phase completion\nUpdate grind.completedPhases]
    U --> B
    D --> V[titan-close reads\ngrind-targets.ndjson +\ntitan-state.json grind block]
Loading

Reviews (2): Last reviewed commit: "fix(skill): add --start-from grind pre-c..." | Re-trigger Greptile

- `.codegraph/titan/issues.ndjson` — issue tracker from all phases
- `.codegraph/titan/arch-snapshot.json` — pre-forge architectural snapshot (communities, structure, drift). Use for before/after comparison in the Metrics section. May not exist if capture failed.
- `.codegraph/titan/drift-report.json` — cumulative drift reports from all phases. May not exist if no drift was detected.
- `.codegraph/titan/grind-targets.ndjson` — grind phase adoption targets and outcomes. Each line: `{target, status, deadBefore, deadAfter, ...}`. May not exist if grind wasn't run.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Schema mismatch between grind-targets.ndjson writer and reader

titan-close expects entries with status: "done"|"failed"|"false_positive" and per-entry deadBefore/deadAfter fields, but titan-grind Step 2f writes entries with classification: "adopt|re-export|promote|..." and no dead-count fields. The grind metrics block in titan-close (count of status: "done", status: "failed", status: "false_positive") will always return 0, and the dead-symbol delta table will have no data.

titan-grind Step 2f writes:

{"target":"...","file":"...","phase":N,"classification":"adopt|remove|...","reason":"...","consumers":[...],"pattern":"...","timestamp":"..."}

titan-close grind metrics reads:

- count of lines with `status: "done"`
- count of lines with `status: "failed"`
- `deadBefore`/`deadAfter` per target

The fix is to align the field names — either titan-grind should write a status field (mapped from classification) plus deadBefore/deadAfter, or titan-close should read classification and compute the counts from titan-state.json's grind.processedTargets/grind.failedTargets/grind.falsePositives lists instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9c3752e. Updated titan-close to read classification from grind-targets.ndjson and pull metrics (processedTargets, failedTargets, falsePositives, deadSymbolDelta) from titan-state.json → grind block instead of the non-existent status/deadBefore/deadAfter fields. Also updated the artifact description on line 68 to document the actual NDJSON schema.

Comment on lines 56 to 65

const batchOpts = {
batchQuery(targets as Array<string | { command: string; target: string }>, opts.db, {
command,
depth: opts.depth ? parseInt(opts.depth as string, 10) : undefined,
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
};

if (isMultiBatch(targets)) {
const data = multiBatchData(targets as MultiBatchItem[], opts.db, batchOpts);
console.log(JSON.stringify(data, null, 2));
} else {
batch(command!, targets as string[], opts.db, batchOpts);
}
});
},
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Missing null guard dropped from isMultiBatch

The old isMultiBatch guard checked targets[0] !== null before 'command' in targets[0]. The new batchQuery checks typeof targets[0] === 'object' which is true for null, then immediately accesses (targets[0] as MultiBatchTarget).command. If a caller ever passes an array with a null first element (valid at runtime even if the union type excludes it), this will throw TypeError: Cannot read properties of null.

Suggested change
const batchOpts = {
batchQuery(targets as Array<string | { command: string; target: string }>, opts.db, {
command,
depth: opts.depth ? parseInt(opts.depth as string, 10) : undefined,
file: opts.file,
kind: opts.kind,
noTests: ctx.resolveNoTests(opts),
};
if (isMultiBatch(targets)) {
const data = multiBatchData(targets as MultiBatchItem[], opts.db, batchOpts);
console.log(JSON.stringify(data, null, 2));
} else {
batch(command!, targets as string[], opts.db, batchOpts);
}
});
},
};
const isMulti =
targets.length > 0 &&
targets[0] !== null &&
typeof targets[0] === 'object' &&
!!(targets[0] as MultiBatchTarget).command;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e77e1f8. Added the targets[0] !== null guard back before the typeof check, since typeof null === 'object' is true in JS and would cause a TypeError when accessing .command.

Comment on lines 277 to 290
rootDir,
analysisOpts,
);
if (needsCfg) {
const { buildCFGData } = await import('../../../../features/cfg.js');
await buildCFGData(db, analysisSymbols, rootDir, engineOpts);
}
if (needsDataflow) {
const { buildDataflowEdges } = await import('../../../../features/dataflow.js');
await buildDataflowEdges(db, analysisSymbols, rootDir, engineOpts);
}
const { runAnalyses } = await import('../../../../ast-analysis/engine.js');
await runAnalyses(
db,
analysisSymbols,
rootDir,
{ ast: false, complexity: false, cfg: needsCfg, dataflow: needsDataflow },
engineOpts,
);
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Unconditional engine import when neither analysis is needed

The old code only dynamically imported cfg.js / dataflow.js when the respective flag was set. The new code always imports ast-analysis/engine.js, even when both needsCfg and needsDataflow are false (the common incremental-build path). This adds a module-load round-trip on every incremental rebuild that requires no supplemental analysis.

Consider guarding the import:

if (needsCfg || needsDataflow) {
  const { runAnalyses } = await import('../../../../ast-analysis/engine.js');
  await runAnalyses(db, analysisSymbols, rootDir,
    { ast: false, complexity: false, cfg: needsCfg, dataflow: needsDataflow },
    engineOpts,
  );
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is actually already guarded. Line 268 has if (!needsCfg && !needsDataflow) return false; which early-returns before the import on line 280 ever executes. The import is only reached when at least one of needsCfg/needsDataflow is true, so there's no unnecessary module-load on the incremental-build path.

…chema (#896)

titan-close was reading status/deadBefore/deadAfter fields that
titan-grind never writes. Updated to read classification field from
grind-targets.ndjson and pull metrics from titan-state.json grind block.
typeof null === 'object' is true in JS, so accessing .command on null
would throw TypeError. Added explicit null check before the typeof.
Without a validation table row for grind, the orchestrator had no
guidance on which pre-checks to run when starting directly from grind.
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Re: P2 — --start-from grind has no pre-checks row

Fixed in 0718ddf. Added a grind row to the pre-checks validation table in titan-run SKILL.md: requires V14, V15, and execution.completedPhases to be non-empty (confirming forge ran at least one phase).

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Codegraph Impact Analysis

1 functions changed1 callers affected across 1 files

  • batchQuery in src/presentation/batch.ts:31 (1 transitive callers)

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 6f6608b into main Apr 9, 2026
27 checks passed
@carlos-alm carlos-alm deleted the feat/adopt-dead-helpers branch April 9, 2026 02:09
@github-actions github-actions bot locked and limited conversation to collaborators Apr 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant