Skip to content

Commit 082a954

Browse files
authored
fix(skills): track real phase timestamps, open GitHub issues, enforce batch size (#702)
* fix(skills): track real phase timestamps, open GitHub issues, enforce batch size titan-run: record phaseTimestamps.{phase}.startedAt/completedAt in titan-state.json at each phase boundary so CLOSE has real wall-clock data. titan-close: read phaseTimestamps from state instead of guessing; fall back to git log timestamps when missing. Open GitHub issues (gh issue create) for bug/limitation severity entries instead of only listing them in the report. titan-recon: enforce hard limit of 5 files per batch to prevent context overload in gauntlet sub-agents. titan-run V4 now warns on oversized batches. * fix(skills): add gh availability check and timestamp validation (#702) Add gh CLI pre-check in titan-close before attempting GitHub issue creation, with graceful fallback when gh is unavailable or unauthenticated. Add startedAt < completedAt timestamp validation in titan-run after recording phase completion timestamps. * fix(skills): address Greptile review feedback (#702) - Guard startedAt against overwrite on resume in titan-run timestamp helper (prevents fabricated short durations after crash/resume) - Fix "codebase" referenced as severity instead of category in titan-close issue creation rules - Replace fragile heredoc-in-subshell pattern with --body-file temp file for gh issue create to avoid quoting/expansion edge cases * fix(skills): record close.completedAt before writing report (#702) titan-run records phaseTimestamps.close.completedAt after titan-close returns, but by then the report is already written. Add an instruction for titan-close to record its own completedAt just before generating the report, so the Pipeline Timeline CLOSE row has accurate data. * fix(skills): use exit(0) for timestamp validation warning (#702) The timestamp validation script used process.exit(1) on clock skew, contradicting the prose instruction to "log a warning but do not stop the pipeline." An AI agent seeing exit code 1 may abort the phase. Changed to process.exit(0) so the warning is non-fatal. * fix(skills): handle recon.startedAt on fresh runs (#702) The recon start timestamp helper threw ENOENT on fresh runs because titan-state.json doesn't exist until titan-recon Step 12. The fix adds a safe variant that creates a minimal stub when the file is missing and updates titan-recon to merge any existing phaseTimestamps into the full state file it writes.
1 parent 6019e58 commit 082a954

3 files changed

Lines changed: 126 additions & 9 deletions

File tree

.claude/skills/titan-close/SKILL.md

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ Compare final metrics against `titan-state.json` baseline:
216216

217217
---
218218

219-
## Step 5 — Compile the issue tracker
219+
## Step 5 — Compile the issue tracker and open GitHub issues
220220

221221
Read `.codegraph/titan/issues.ndjson`. Each line is a JSON object:
222222

@@ -230,6 +230,47 @@ Group issues by category and severity. Summarize:
230230
- **Process notes:** suggestions for improving the Titan workflow
231231
- **Codebase observations:** structural concerns beyond what the audit covered
232232

233+
### 5b. Open GitHub issues
234+
235+
**Pre-check:** Verify `gh` is available and authenticated before attempting issue creation:
236+
```bash
237+
gh auth status 2>&1 || echo "GH_UNAVAILABLE"
238+
```
239+
If `GH_UNAVAILABLE`, skip issue creation entirely and note in the report: "GitHub issues were not created — `gh` CLI is not available or not authenticated. Create them manually from the Issues section below."
240+
241+
For each issue with severity `bug` or `limitation`, create a GitHub issue using `gh`:
242+
243+
```bash
244+
BODY=$(mktemp)
245+
cat > "$BODY" <<'ISSUE_BODY'
246+
## Context
247+
Discovered during Titan audit (phase: <phase>, date: <timestamp>).
248+
249+
## Description
250+
<description>
251+
252+
## Additional Context
253+
<context field, if present>
254+
255+
## Source
256+
- **Titan phase:** <phase>
257+
- **Severity:** <severity>
258+
- **Category:** <category>
259+
ISSUE_BODY
260+
gh issue create --title "<category>: <short description>" --body-file "$BODY" --label "titan-audit"
261+
rm -f "$BODY"
262+
```
263+
264+
Using `--body-file` with a temp file avoids quoting/expansion issues that can arise when issue descriptions contain backticks, `$()` sequences, or literal `EOF` strings.
265+
266+
**Rules for issue creation:**
267+
- **Only open issues for `bug` and `limitation` severity.** Suggestions and observations go in the report only — they are not actionable enough for standalone issues.
268+
- **Check for duplicates first:** Run `gh issue list --search "<short description>" --state open --limit 5` before creating. If a matching open issue exists, skip it and note "existing issue #N" in the report.
269+
- **Label:** Use `titan-audit` label. If the label doesn't exist, create it: `gh label create titan-audit --description "Issues discovered during Titan audit" --color "d4c5f9" 2>/dev/null || true`
270+
- **Record each created issue number** for inclusion in the report's Issues section.
271+
272+
For `suggestion` severity entries and entries with `category: "codebase"`, include them in the report's Issues section but do NOT create GitHub issues.
273+
233274
---
234275

235276
## Step 6 — Compile the gate log
@@ -244,6 +285,14 @@ Read `.codegraph/titan/gate-log.ndjson`. Summarize:
244285

245286
## Step 7 — Generate the report
246287

288+
### Record CLOSE completion timestamp
289+
290+
Before writing the report, record `phaseTimestamps.close.completedAt` so the Pipeline Timeline has accurate data for the CLOSE row. (titan-run also records this after titan-close returns as a safety backstop, but by then the report is already written.)
291+
292+
```bash
293+
node -e "const fs=require('fs');const s=JSON.parse(fs.readFileSync('.codegraph/titan/titan-state.json','utf8'));s.phaseTimestamps=s.phaseTimestamps||{};s.phaseTimestamps['close']=s.phaseTimestamps['close']||{};s.phaseTimestamps['close'].completedAt=new Date().toISOString();fs.writeFileSync('.codegraph/titan/titan-state.json',JSON.stringify(s,null,2));"
294+
```
295+
247296
### Report path
248297

249298
```
@@ -283,13 +332,21 @@ Write the report as Markdown:
283332

284333
## Pipeline Timeline
285334

286-
| Phase | Started | Completed | Duration |
287-
|-------|---------|-----------|----------|
288-
| RECON | <from state> | <from state> ||
289-
| GAUNTLET ||||
290-
| SYNC ||||
291-
| GATE (runs) ||||
292-
| CLOSE | <now> | <now> ||
335+
Read `titan-state.json → phaseTimestamps` for real wall-clock data. If `phaseTimestamps` exists, use the recorded ISO 8601 timestamps to compute durations. If it does not exist (older pipeline run), derive timing from git commit timestamps as a fallback — **never invent or guess timestamps.**
336+
337+
**Duration computation:** For each phase with `startedAt` and `completedAt`, compute duration as the difference in minutes/hours. For forge, also note the first and last commit timestamps from `git log`.
338+
339+
| Phase | Duration | Notes |
340+
|-------|----------|-------|
341+
| RECON | <computed from phaseTimestamps.recon> ||
342+
| GAUNTLET | <computed from phaseTimestamps.gauntlet> | <iterations count if resuming> |
343+
| SYNC | <computed from phaseTimestamps.sync> ||
344+
| FORGE | <computed from phaseTimestamps.forge> | <commit count>, first at <time>, last at <time> |
345+
| GATE | across forge | <total runs> inline with forge commits |
346+
| CLOSE | <computed from phaseTimestamps.close> ||
347+
| **Total** | <sum of all phases> ||
348+
349+
**If `phaseTimestamps` is missing:** Fall back to git log timestamps. Use the earliest and latest commit timestamps from `git log main..HEAD --format="%ai"` to bound the forge phase. For analysis phases (recon, gauntlet, sync), use `titan-state.json → initialized` and `lastUpdated` as rough bounds. Mark the durations as "~approximate" in the table.
293350

294351
---
295352

.claude/skills/titan-recon/SKILL.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ Write `.codegraph/titan/GLOBAL_ARCH.md`:
188188

189189
## Step 10 — Propose work batches
190190

191-
Decompose the priority queue into **work batches** of ~5-15 files each:
191+
Decompose the priority queue into **work batches** of **at most 5 files each**:
192+
- **Hard limit: 5 files per batch.** If a domain has more than 5 files, split it into multiple batches (e.g., "domain-parser-1", "domain-parser-2"). This keeps each gauntlet iteration focused and prevents context overload in sub-agents.
192193
- Stay within a single domain where possible
193194
- Group tightly-coupled files together (from communities)
194195
- Order by priority: highest-risk domains first
@@ -214,6 +215,14 @@ Create `.codegraph/titan/titan-state.json` — the single source of truth for th
214215
mkdir -p .codegraph/titan
215216
```
216217

218+
**Important:** Before writing, check if `titan-state.json` already exists (the orchestrator may have written `phaseTimestamps.recon.startedAt` before dispatching this sub-agent). If it does, read the existing `phaseTimestamps` and merge them into the new state object so the start timestamp is preserved:
219+
220+
```bash
221+
node -e "const fs=require('fs');const p='.codegraph/titan/titan-state.json';let existing={};try{existing=JSON.parse(fs.readFileSync(p,'utf8'));}catch{}console.log(JSON.stringify(existing.phaseTimestamps||{}));"
222+
```
223+
224+
Include the preserved `phaseTimestamps` in the state file below.
225+
217226
```json
218227
{
219228
"version": 1,
@@ -281,6 +290,7 @@ mkdir -p .codegraph/titan
281290
},
282291
"hotFiles": ["<top 30>"],
283292
"tangledDirs": ["<cohesion < 0.3>"],
293+
"phaseTimestamps": "<merged from existing file — see above>",
284294
"fileAudits": {},
285295
"progress": {
286296
"totalFiles": 0,

.claude/skills/titan-run/SKILL.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ You are the **orchestrator** for the full Titan Paradigm pipeline. Your job is t
4040
- If state exists and `--start-from` not specified, ask user: "Existing Titan state found (phase: `<currentPhase>`). Resume from current state, or start fresh with `/titan-reset` first?"
4141
- If `--yes` is set, resume automatically.
4242

43+
**Initialize the phase timestamps helper.** Throughout the pipeline, you will record wall-clock timestamps for each phase. Use this helper to write them into `titan-state.json`:
44+
45+
```bash
46+
# Record phase start (safe for resume — only sets startedAt if not already present):
47+
node -e "const fs=require('fs');const s=JSON.parse(fs.readFileSync('.codegraph/titan/titan-state.json','utf8'));s.phaseTimestamps=s.phaseTimestamps||{};s.phaseTimestamps['<PHASE>']=s.phaseTimestamps['<PHASE>']||{};if(!s.phaseTimestamps['<PHASE>'].startedAt){s.phaseTimestamps['<PHASE>'].startedAt=new Date().toISOString();fs.writeFileSync('.codegraph/titan/titan-state.json',JSON.stringify(s,null,2));}"
48+
49+
# Record phase completion:
50+
node -e "const fs=require('fs');const s=JSON.parse(fs.readFileSync('.codegraph/titan/titan-state.json','utf8'));s.phaseTimestamps=s.phaseTimestamps||{};s.phaseTimestamps['<PHASE>']=s.phaseTimestamps['<PHASE>']||{};s.phaseTimestamps['<PHASE>'].completedAt=new Date().toISOString();fs.writeFileSync('.codegraph/titan/titan-state.json',JSON.stringify(s,null,2));"
51+
```
52+
53+
Replace `<PHASE>` with `recon`, `gauntlet`, `sync`, `forge`, or `close`. **Run the start command immediately before dispatching each phase's first sub-agent, and the completion command immediately after post-phase validation passes.** If resuming a phase (e.g., gauntlet loop iteration 2+), do NOT overwrite `startedAt` — only set it if it doesn't already exist.
54+
55+
**Timestamp validation:** After recording `completedAt` for any phase, verify `startedAt < completedAt`:
56+
```bash
57+
node -e "const s=JSON.parse(require('fs').readFileSync('.codegraph/titan/titan-state.json','utf8'));const p=s.phaseTimestamps?.['<PHASE>'];if(p?.startedAt&&p?.completedAt){const start=new Date(p.startedAt),end=new Date(p.completedAt);if(end<=start){console.log('WARNING: <PHASE> completedAt ('+p.completedAt+') is not after startedAt ('+p.startedAt+')');process.exit(0);}console.log('<PHASE> duration: '+((end-start)/60000).toFixed(1)+' min');}else{console.log('WARNING: <PHASE> missing startedAt or completedAt');}"
58+
```
59+
If the check fails, log a warning but do not stop the pipeline — clock skew or immediate completion of short phases can cause this.
60+
4361
4. **Sync with main** (once, before any sub-agent runs):
4462
```bash
4563
git fetch origin main && git merge origin/main --no-edit
@@ -128,6 +146,17 @@ WARN-level V-checks from skipped phases are surfaced as prefixed warnings: "[ski
128146

129147
### 1a. Run Pre-Agent Gate (G1-G4)
130148

149+
### 1a.1. Record phase start timestamp
150+
Record `phaseTimestamps.recon.startedAt` (only if not already set — it may exist from a prior crashed run).
151+
152+
**Note:** On a fresh run, `titan-state.json` does not yet exist (titan-recon creates it in Step 12). Use this safe variant that creates a minimal stub if the file is missing:
153+
154+
```bash
155+
node -e "const fs=require('fs');const p='.codegraph/titan/titan-state.json';let s;try{s=JSON.parse(fs.readFileSync(p,'utf8'));}catch{fs.mkdirSync('.codegraph/titan',{recursive:true});s={};}s.phaseTimestamps=s.phaseTimestamps||{};s.phaseTimestamps['recon']=s.phaseTimestamps['recon']||{};if(!s.phaseTimestamps['recon'].startedAt){s.phaseTimestamps['recon'].startedAt=new Date().toISOString();fs.writeFileSync(p,JSON.stringify(s,null,2));}"
156+
```
157+
158+
This ensures `recon.startedAt` is recorded even on first-time runs. titan-recon Step 12 merges any existing `phaseTimestamps` into the full state file it writes.
159+
131160
### 1b. Dispatch sub-agent
132161

133162
Use the **Agent tool** to spawn a sub-agent:
@@ -177,11 +206,14 @@ If `NO_SNAPSHOT` → **WARN** (not fatal, but note it: "No baseline snapshot —
177206
**V4. Cross-check counts:**
178207
- `titan-state.json → stats.totalFiles` should roughly match the number of targets across all batches (batches are subsets of files, so `sum(batch.files.length)` should be ≤ `totalFiles`)
179208
- `priorityQueue.length` should be > 0 and ≤ `totalNodes`
209+
- **Batch size check:** Every batch must have ≤ 5 files. If any batch exceeds 5, **WARN**: "Batch <id> has <N> files (max 5). Large batches cause context overload in gauntlet sub-agents."
180210

181211
If wildly inconsistent (e.g., 0 batches but 500 nodes) → **WARN** with details.
182212

183213
Print: `RECON validated. Domains: <count>, Batches: <count>, Priority targets: <count>, Quality score: <score>`
184214

215+
Record `phaseTimestamps.recon.completedAt`.
216+
185217
---
186218

187219
## Step 2 — GAUNTLET (loop)
@@ -190,6 +222,8 @@ Print: `RECON validated. Domains: <count>, Batches: <count>, Priority targets: <
190222

191223
### 2a. Pre-loop check
192224

225+
Record `phaseTimestamps.gauntlet.startedAt` (only if not already set — gauntlet may be resuming).
226+
193227
Read `.codegraph/titan/gauntlet-summary.json` if it exists:
194228
- If `"complete": true` → run gauntlet post-validation (2d) and skip loop if it passes
195229
- Otherwise, count completed batches from `titan-state.json` for progress tracking
@@ -298,6 +332,8 @@ If mismatched → **WARN** with details (not fatal — the NDJSON is the source
298332
299333
Print: `GAUNTLET validated. Audited: <N>/<M> targets. Pass: <N>, Warn: <N>, Fail: <N>, Decompose: <N>. NDJSON integrity: <valid>/<total> lines OK.`
300334
335+
Record `phaseTimestamps.gauntlet.completedAt`.
336+
301337
---
302338
303339
## Step 3 — SYNC
@@ -306,6 +342,9 @@ Print: `GAUNTLET validated. Audited: <N>/<M> targets. Pass: <N>, Warn: <N>, Fail
306342
307343
### 3a. Run Pre-Agent Gate (G1-G4)
308344
345+
### 3a.1. Record phase start timestamp
346+
Record `phaseTimestamps.sync.startedAt`.
347+
309348
### 3b. Dispatch sub-agent
310349
311350
```
@@ -337,6 +376,8 @@ For entries with `dependencies` arrays, verify that each dependency phase number
337376
338377
Print: `SYNC validated. Execution phases: <N>, Total targets: <N>, Estimated commits: <N>.`
339378
379+
Record `phaseTimestamps.sync.completedAt`.
380+
340381
---
341382
342383
## Step 3.5 — Pre-forge: Architectural Snapshot + Human Checkpoint
@@ -453,6 +494,8 @@ Once the user confirms (or `--yes` was set), `autoConfirm` is already `true` (se
453494
454495
### 4a. Pre-loop check
455496
497+
Record `phaseTimestamps.forge.startedAt` (only if not already set — forge may be resuming).
498+
456499
Read `.codegraph/titan/sync.json` → count total phases in `executionOrder`.
457500
Read `.codegraph/titan/titan-state.json` → check `execution.completedPhases` (may not exist yet if forge hasn't started).
458501
@@ -575,6 +618,8 @@ If `.codegraph/titan/gate-log.ndjson` exists:
575618
576619
Print forge summary.
577620
621+
Record `phaseTimestamps.forge.completedAt`.
622+
578623
---
579624
580625
## Step 5 — CLOSE (report + PRs)
@@ -583,6 +628,9 @@ After forge completes, dispatch `/titan-close` to produce the final report with
583628
584629
### 5a. Run Pre-Agent Gate (G1-G4)
585630
631+
### 5a.1. Record phase start timestamp
632+
Record `phaseTimestamps.close.startedAt`.
633+
586634
### 5b. Dispatch sub-agent
587635
588636
```
@@ -598,6 +646,8 @@ After the agent returns, verify:
598646
599647
If the agent created PRs, print the PR URLs.
600648
649+
Record `phaseTimestamps.close.completedAt`.
650+
601651
---
602652
603653
## Error Handling

0 commit comments

Comments
 (0)