devflow-kit 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +4 -4
  3. package/dist/agents/{diagnose.md → debug.md} +4 -4
  4. package/dist/agents/{code-fix.md → fix.md} +2 -2
  5. package/dist/agents/{scrutinize.md → harden.md} +6 -6
  6. package/dist/agents/{code-edit.md → refactor.md} +3 -3
  7. package/dist/cli/agents-view/render.js +1 -1
  8. package/dist/cli/commands/init.js +3 -3
  9. package/dist/cli/commands/learning.js +12 -12
  10. package/dist/cli/commands/legacy-hooks.js +1 -1
  11. package/dist/cli/commands/uninstall.js +111 -5
  12. package/dist/commands/bug-analysis.md +19 -15
  13. package/dist/commands/code-review.md +13 -22
  14. package/dist/commands/debug.md +15 -15
  15. package/dist/commands/dynamic-build.md +51 -55
  16. package/dist/commands/dynamic-plan.md +11 -9
  17. package/dist/commands/dynamic-profile.md +7 -5
  18. package/dist/commands/dynamic-tickets.md +11 -9
  19. package/dist/commands/explore.md +13 -13
  20. package/dist/commands/implement.md +58 -127
  21. package/dist/commands/plan.md +19 -41
  22. package/dist/commands/release.md +9 -5
  23. package/dist/commands/research.md +12 -8
  24. package/dist/commands/resolve.md +42 -86
  25. package/dist/commands/self-review.md +42 -42
  26. package/dist/core/agent-models.js +47 -11
  27. package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
  28. package/dist/core/learning-queue-cleanup.js +1 -1
  29. package/dist/core/migrations.js +63 -44
  30. package/dist/core/plugins.js +14 -12
  31. package/dist/core/project-paths.js +3 -3
  32. package/dist/hud/codex-usage.js +247 -0
  33. package/dist/hud/components/codex-quota.js +37 -0
  34. package/dist/hud/components/usage-quota.js +1 -1
  35. package/dist/hud/config.js +1 -0
  36. package/dist/hud/index.js +16 -3
  37. package/dist/hud/render.js +3 -1
  38. package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
  39. package/dist/learning-off/agents/{code-fix.md → fix.md} +2 -2
  40. package/dist/learning-off/agents/{scrutinize.md → harden.md} +6 -6
  41. package/dist/learning-off/agents/{code-edit.md → refactor.md} +3 -3
  42. package/dist/learning-off/commands/bug-analysis.md +9 -9
  43. package/dist/learning-off/commands/code-review.md +2 -15
  44. package/dist/learning-off/commands/debug.md +4 -8
  45. package/dist/learning-off/commands/dynamic-build.md +45 -51
  46. package/dist/learning-off/commands/dynamic-plan.md +5 -5
  47. package/dist/learning-off/commands/dynamic-profile.md +1 -1
  48. package/dist/learning-off/commands/dynamic-tickets.md +5 -5
  49. package/dist/learning-off/commands/explore.md +2 -6
  50. package/dist/learning-off/commands/implement.md +46 -119
  51. package/dist/learning-off/commands/plan.md +8 -33
  52. package/dist/learning-off/commands/release.md +4 -2
  53. package/dist/learning-off/commands/research.md +2 -2
  54. package/dist/learning-off/commands/resolve.md +31 -79
  55. package/dist/learning-off/commands/self-review.md +30 -34
  56. package/dist/skills/git/references/pr/check-merge-readiness.md +2 -0
  57. package/dist/skills/git/references/pr/update-pr-evidence.md +4 -6
  58. package/dist/targets/claude-code/installer.js +117 -84
  59. package/dist/targets/claude-code/tracker-servers.js +1 -1
  60. package/package.json +1 -1
  61. package/src/assets/agents/_partials/_code.mds +2 -2
  62. package/src/assets/agents/code.mds +2 -2
  63. package/src/assets/agents/{learning.md → curate.md} +4 -4
  64. package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
  65. package/src/assets/agents/evaluate.md +4 -4
  66. package/src/assets/agents/{code-fix.mds → fix.mds} +5 -5
  67. package/src/assets/agents/{scrutinize.mds → harden.mds} +6 -6
  68. package/src/assets/agents/{simplify.md → polish.md} +4 -4
  69. package/src/assets/agents/{code-edit.mds → refactor.mds} +6 -6
  70. package/src/assets/agents/test.md +2 -2
  71. package/src/assets/commands/_partials/_compliance.mds +1 -1
  72. package/src/assets/commands/_partials/_decisions.mds +26 -6
  73. package/src/assets/commands/_partials/_engine.mds +15 -15
  74. package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
  75. package/src/assets/commands/_partials/_knowledge.mds +14 -9
  76. package/src/assets/commands/_partials/_preamble.mds +4 -2
  77. package/src/assets/commands/_partials/_roster.mds +4 -4
  78. package/src/assets/commands/bug-analysis.mds +7 -7
  79. package/src/assets/commands/code-review.mds +1 -14
  80. package/src/assets/commands/debug.mds +2 -2
  81. package/src/assets/commands/dynamic-build.mds +26 -32
  82. package/src/assets/commands/implement.mds +45 -102
  83. package/src/assets/commands/plan.mds +7 -37
  84. package/src/assets/commands/release.mds +5 -1
  85. package/src/assets/commands/resolve.mds +28 -76
  86. package/src/assets/commands/self-review.mds +27 -27
  87. package/src/assets/mds/git/_pr.mds +6 -6
  88. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
  89. package/src/assets/scripts/hooks/capture-prompt +1 -1
  90. package/src/assets/scripts/hooks/capture-question +1 -1
  91. package/src/assets/scripts/hooks/capture-turn +1 -1
  92. package/src/assets/scripts/hooks/json-helper.cjs +2 -2
  93. package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
  94. package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -4
  95. package/src/assets/scripts/hooks/preamble +1 -1
  96. package/src/assets/scripts/hooks/session-start-context +58 -45
  97. package/src/assets/scripts/verify-evidence.cjs +726 -9
  98. package/src/assets/skills/agent-references/SKILL.md +9 -5
  99. package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
  100. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
  101. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +1 -1
  102. package/src/assets/skills/apply-decisions/SKILL.md +6 -4
  103. package/src/assets/skills/docs-framework/SKILL.md +3 -3
  104. package/src/assets/skills/qa/SKILL.md +1 -1
  105. package/src/assets/skills/quality-gates/SKILL.md +7 -7
  106. package/src/assets/skills/quality-gates/references/report-template.md +7 -7
  107. /package/src/assets/skills/agent-references/references/{learning → curate}/maintain.md +0 -0
@@ -34,7 +34,7 @@ export const DEVFLOW_PLUGINS = [
34
34
  *
35
35
  * Guard 5 reverse (registry-integrity.test.ts, "declared agents are spawned")
36
36
  * skips a plugin whose commands spawn nothing — `if (spawned.size === 0)
37
- * continue` — and this plugin ships no commands at all. So `learning` and
37
+ * continue` — and this plugin ships no commands at all. So `curate` and
38
38
  * `tracker`, which are spawned by a SessionStart directive rather than by any
39
39
  * command, satisfy the reverse check STRUCTURALLY.
40
40
  *
@@ -48,11 +48,13 @@ export const DEVFLOW_PLUGINS = [
48
48
  * set-equal, both directions, against the `agentType` values present in
49
49
  * dist/commands/, and a hook-spawned agent appears in none of them.
50
50
  */
51
- agents: ['learning', 'tracker'],
51
+ agents: ['curate', 'tracker'],
52
52
  /**
53
53
  * D-AGENT-REFS: `agent-references` is a reference-only skill. It holds the Synthesize mode
54
- * templates and the Learning maintain step, which those two agents read by explicit path, so
55
- * it sits here (core-skills is always installed) and no other plugin lists or requires it.
54
+ * templates and the Curate maintain step, which those two agents read by explicit path, and
55
+ * the command branches that D-CMD-REF-HAND moves out of a command (`references/commands/`),
56
+ * which a command reads by explicit path, so it sits here (core-skills is always installed)
57
+ * and no other plugin lists or requires it.
56
58
  * It is never preloaded: tests/guards/agent-references.test.ts holds that, and the one
57
59
  * exception to the repo-wide ban on `disable-model-invocation` names this skill alone.
58
60
  */
@@ -102,7 +104,7 @@ export const DEVFLOW_PLUGINS = [
102
104
  name: 'devflow-implement',
103
105
  description: 'Complete task implementation workflow - accepts plan documents, issues, or task descriptions',
104
106
  commands: ['/implement'],
105
- agents: ['git', 'code', 'code-fix', 'simplify', 'scrutinize', 'evaluate', 'test', 'validate', 'knowledge'],
107
+ agents: ['git', 'code', 'fix', 'polish', 'harden', 'evaluate', 'test', 'validate', 'knowledge'],
106
108
  skills: ['patterns', 'qa', 'quality-gates', 'worktree-support', 'feature-knowledge', 'apply-feature-knowledge'],
107
109
  requires: [
108
110
  'apply-decisions',
@@ -141,7 +143,7 @@ export const DEVFLOW_PLUGINS = [
141
143
  name: 'devflow-resolve',
142
144
  description: 'Process and fix code review issues with blast-radius triage, Code agent fixes, and Validate agent verification',
143
145
  commands: ['/resolve'],
144
- agents: ['git', 'triage', 'code', 'code-fix', 'simplify', 'validate', 'test', 'knowledge'],
146
+ agents: ['git', 'triage', 'code', 'fix', 'polish', 'validate', 'test', 'knowledge'],
145
147
  skills: ['patterns', 'worktree-support', 'feature-knowledge', 'apply-feature-knowledge', 'apply-decisions'],
146
148
  requires: [
147
149
  'architecture',
@@ -167,7 +169,7 @@ export const DEVFLOW_PLUGINS = [
167
169
  name: 'devflow-debug',
168
170
  description: 'Debugging workflows with competing hypothesis investigation via parallel subagents',
169
171
  commands: ['/debug'],
170
- agents: ['git', 'synthesize', 'simplify', 'knowledge', 'explore', 'code'],
172
+ agents: ['git', 'synthesize', 'polish', 'knowledge', 'explore', 'code'],
171
173
  skills: ['git', 'worktree-support', 'feature-knowledge', 'apply-feature-knowledge'],
172
174
  // D-DEBUG-FIX-VIA-CODE: the fix is a Code spawn. Code loads no skill by language or mode
173
175
  // (D-NO-RUNTIME-SKILLS), so `requires:` lists only the skills this command, its agents and the
@@ -232,9 +234,9 @@ export const DEVFLOW_PLUGINS = [
232
234
  },
233
235
  {
234
236
  name: 'devflow-self-review',
235
- description: 'Self-review workflow: Simplify agent + Scrutinize agent for code quality',
237
+ description: 'Self-review workflow: Polish agent + Harden agent for code quality',
236
238
  commands: ['/self-review'],
237
- agents: ['simplify', 'scrutinize', 'validate', 'knowledge'],
239
+ agents: ['polish', 'harden', 'validate', 'knowledge'],
238
240
  skills: ['quality-gates', 'software-design', 'worktree-support', 'feature-knowledge', 'apply-feature-knowledge'],
239
241
  // D-PRELOAD-TRIM: no agent, command or skill in this plugin's corpus names
240
242
  // `testing` (Validate preloads only `worktree-support`), so it is not required here.
@@ -245,7 +247,7 @@ export const DEVFLOW_PLUGINS = [
245
247
  name: 'devflow-bug-analysis',
246
248
  description: 'Proactive bug finding with static and semantic analysis',
247
249
  commands: ['/bug-analysis'],
248
- agents: ['git', 'diagnose', 'synthesize'],
250
+ agents: ['git', 'debug', 'synthesize'],
249
251
  skills: [
250
252
  'apply-decisions',
251
253
  'apply-feature-knowledge',
@@ -272,7 +274,7 @@ export const DEVFLOW_PLUGINS = [
272
274
  name: 'devflow-ambient',
273
275
  description: 'Orchestrator ambient mode — session charter, per-prompt reminder, plan handoff',
274
276
  commands: [],
275
- agents: ['code', 'code-edit', 'validate', 'simplify', 'scrutinize', 'evaluate', 'test', 'skim', 'review', 'git', 'synthesize', 'triage', 'design', 'knowledge', 'research', 'learning', 'explore'],
277
+ agents: ['code', 'refactor', 'validate', 'polish', 'harden', 'evaluate', 'test', 'skim', 'review', 'git', 'synthesize', 'triage', 'design', 'knowledge', 'research', 'curate', 'explore'],
276
278
  skills: [
277
279
  'review-methodology',
278
280
  'security',
@@ -313,7 +315,7 @@ export const DEVFLOW_PLUGINS = [
313
315
  description: 'Dynamic workflow recipes - dependency-aware tickets→plan→build delivery pipeline',
314
316
  // Commands compiled from commands/*.mds at build time (build:mds).
315
317
  commands: ['/dynamic-tickets', '/dynamic-plan', '/dynamic-build', '/dynamic-profile'],
316
- agents: ['code', 'code-fix', 'validate', 'simplify', 'scrutinize', 'evaluate', 'test', 'review', 'git', 'synthesize', 'knowledge', 'design'],
318
+ agents: ['code', 'fix', 'validate', 'polish', 'harden', 'evaluate', 'test', 'review', 'git', 'synthesize', 'knowledge', 'design'],
317
319
  skills: ['apply-decisions', 'apply-feature-knowledge', 'worktree-support', 'docs-framework'],
318
320
  requires: [
319
321
  'architecture',
@@ -46,7 +46,7 @@ export function getFeatureConfigPath(projectRoot) {
46
46
  export function getLearningPendingTurnsPath(projectRoot) {
47
47
  return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.jsonl');
48
48
  }
49
- /** .devflow/learning/.pending-turns.processing — the claimed batch a Learning run holds while it processes it */
49
+ /** .devflow/learning/.pending-turns.processing — the claimed batch a Curate run holds while it processes it */
50
50
  export function getLearningPendingTurnsProcessingPath(projectRoot) {
51
51
  return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.processing');
52
52
  }
@@ -65,8 +65,8 @@ export function getDecisionsFilePath(projectRoot) {
65
65
  export function getPitfallsFilePath(projectRoot) {
66
66
  return path.join(projectRoot, '.devflow', 'learning', 'pitfalls.md');
67
67
  }
68
- /** .devflow/learning/learning.json — project-level learning agent tuning config */
69
- export function getLearningTuningConfigPath(projectRoot) {
68
+ /** .devflow/learning/learning.json — project-level curate agent tuning config */
69
+ export function getCurateTuningConfigPath(projectRoot) {
70
70
  return path.join(projectRoot, '.devflow', 'learning', 'learning.json');
71
71
  }
72
72
  /** .devflow/learning/decisions-ledger.jsonl — anchored ledger rows (single source of truth for rendering) */
@@ -0,0 +1,247 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readCache, writeCache, hudCacheDir } from '../core/cache.js';
4
+ const CODEX_CACHE_KEY = 'codex-quota';
5
+ const CODEX_CACHE_TTL_MS = 30_000;
6
+ /** Rollouts last modified longer ago than this are not read. */
7
+ export const CODEX_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1000;
8
+ /** The first read of a rollout, and the one larger read when it holds no record. */
9
+ export const CODEX_TAIL_BYTES = 1024 * 1024;
10
+ export const CODEX_TAIL_RETRY_BYTES = 4 * 1024 * 1024;
11
+ const MAX_YEAR_DIRS = 3;
12
+ const MAX_DAY_DIRS = 14;
13
+ const MAX_ROLLOUTS_PER_DAY = 200;
14
+ const MAX_ROLLOUTS_READ = 3;
15
+ const YEAR_DIR = /^\d{4}$/;
16
+ const MONTH_DIR = /^(0[1-9]|1[0-2])$/;
17
+ const DAY_DIR = /^(0[1-9]|[12]\d|3[01])$/;
18
+ const ROLLOUT_FILE = /^rollout-.*\.jsonl$/;
19
+ /**
20
+ * D-CODEX-LOCAL-LOGS-ONLY: Codex usage comes from the rate-limit records the
21
+ * Codex CLI already writes to its own session logs under ~/.codex/sessions. The
22
+ * statusline reads those logs and nothing else: it opens no credential file
23
+ * (auth.json) and makes no network call, so it cannot leak a token or stall on a
24
+ * request. Reason: the HUD runs on every prompt under a 2 s budget, and Codex
25
+ * publishes the account's limits in every model response it logs.
26
+ *
27
+ * The directory is always <home>/.codex, as the proxy's Codex credential check
28
+ * resolves it; a CODEX_HOME override is not honoured.
29
+ */
30
+ export function codexSessionsDir(home) {
31
+ return path.join(home, '.codex', 'sessions');
32
+ }
33
+ function asRecord(value) {
34
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
35
+ ? value
36
+ : null;
37
+ }
38
+ function isFiniteNumber(value) {
39
+ return typeof value === 'number' && Number.isFinite(value);
40
+ }
41
+ function makeWindow(minutes, usedPercent, resetsAt) {
42
+ if (!isFiniteNumber(minutes) || minutes <= 0 || !isFiniteNumber(usedPercent) || !isFiniteNumber(resetsAt)) {
43
+ return null;
44
+ }
45
+ return { minutes, usedPercent: Math.max(0, Math.min(100, usedPercent)), resetsAt };
46
+ }
47
+ function makeUsage(windows, limitReached) {
48
+ if (windows.length === 0)
49
+ return null;
50
+ return {
51
+ windows: [...windows].sort((a, b) => a.minutes - b.minutes),
52
+ limitReached: typeof limitReached === 'string' && limitReached !== '' ? limitReached : null,
53
+ };
54
+ }
55
+ /**
56
+ * D-CODEX-WINDOW-BY-MINUTES: a window is identified by its window_minutes, never
57
+ * by the primary/secondary slot it arrives in. Reason: the slots carry different
58
+ * windows per plan (a prolite plan has the weekly window as primary and no
59
+ * secondary, a team plan a 5h primary and a weekly secondary), so a slot-keyed
60
+ * reader would label one plan's weekly window as the other's 5h one.
61
+ *
62
+ * Parses one rollout line into usage. Accepts only a token_count event whose
63
+ * rate_limits are the codex limit with at least one complete window; premium
64
+ * records, null windows, `rate_limits: null` and malformed lines are skipped.
65
+ */
66
+ function parseUsageLine(line) {
67
+ let record;
68
+ try {
69
+ record = JSON.parse(line);
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ const payload = asRecord(asRecord(record)?.payload);
75
+ if (payload === null || payload.type !== 'token_count')
76
+ return null;
77
+ const limits = asRecord(payload.rate_limits);
78
+ if (limits === null || limits.limit_id !== 'codex')
79
+ return null;
80
+ const windows = [];
81
+ for (const slot of [limits.primary, limits.secondary]) {
82
+ const raw = asRecord(slot);
83
+ const parsed = raw && makeWindow(raw.window_minutes, raw.used_percent, raw.resets_at);
84
+ if (parsed)
85
+ windows.push(parsed);
86
+ }
87
+ return makeUsage(windows, limits.rate_limit_reached_type);
88
+ }
89
+ /** The last line in `text` that holds a valid record, scanning backwards. */
90
+ function lastUsageIn(text) {
91
+ const lines = text.split('\n');
92
+ for (let i = lines.length - 1; i >= 0; i--) {
93
+ // Cheap prefilter: most lines are not token_count events and are never parsed.
94
+ if (!lines[i].includes('"token_count"'))
95
+ continue;
96
+ const usage = parseUsageLine(lines[i]);
97
+ if (usage)
98
+ return usage;
99
+ }
100
+ return null;
101
+ }
102
+ /** Read the last `bytes` bytes of `file`. `whole` is true when that covered the entire file. */
103
+ function readTail(file, bytes) {
104
+ let fd;
105
+ try {
106
+ fd = fs.openSync(file, 'r');
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ try {
112
+ const size = fs.fstatSync(fd).size;
113
+ const length = Math.min(size, bytes);
114
+ const buf = Buffer.alloc(length);
115
+ let total = 0;
116
+ while (total < length) {
117
+ const read = fs.readSync(fd, buf, total, length - total, size - length + total);
118
+ if (read === 0)
119
+ break;
120
+ total += read;
121
+ }
122
+ return { text: buf.toString('utf-8', 0, total), whole: length === size };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ finally {
128
+ fs.closeSync(fd);
129
+ }
130
+ }
131
+ /**
132
+ * The newest valid record in `file`, from its tail only. The first read takes
133
+ * CODEX_TAIL_BYTES; if that holds no record and the file is longer, one retry takes
134
+ * CODEX_TAIL_RETRY_BYTES. Rollouts reach tens of MB, and a tail holds the newest
135
+ * records.
136
+ */
137
+ function latestUsageInFile(file) {
138
+ for (const bytes of [CODEX_TAIL_BYTES, CODEX_TAIL_RETRY_BYTES]) {
139
+ const tail = readTail(file, bytes);
140
+ if (tail === null)
141
+ return null;
142
+ const usage = lastUsageIn(tail.text);
143
+ if (usage !== null || tail.whole)
144
+ return usage;
145
+ }
146
+ return null;
147
+ }
148
+ /** Names of the entries of `dir` of one kind matching `pattern`, newest (greatest) first. */
149
+ function listNames(dir, kind, pattern) {
150
+ try {
151
+ return fs
152
+ .readdirSync(dir, { withFileTypes: true })
153
+ .filter((e) => (kind === 'dir' ? e.isDirectory() : e.isFile()) && pattern.test(e.name))
154
+ .map((e) => e.name)
155
+ .sort()
156
+ .reverse();
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ }
162
+ /** The newest MAX_DAY_DIRS day directories, by name: sessions/YYYY/MM/DD. */
163
+ function recentDayDirs(sessionsDir) {
164
+ const days = [];
165
+ for (const year of listNames(sessionsDir, 'dir', YEAR_DIR).slice(0, MAX_YEAR_DIRS)) {
166
+ for (const month of listNames(path.join(sessionsDir, year), 'dir', MONTH_DIR)) {
167
+ for (const day of listNames(path.join(sessionsDir, year, month), 'dir', DAY_DIR)) {
168
+ if (days.length >= MAX_DAY_DIRS)
169
+ return days;
170
+ days.push(path.join(sessionsDir, year, month, day));
171
+ }
172
+ }
173
+ }
174
+ return days;
175
+ }
176
+ /**
177
+ * D-CODEX-NEWEST-MTIME: the newest data is in the most recently modified rollout,
178
+ * not the newest-named one. Reason: a day directory is named for the day its
179
+ * session started, and Codex keeps appending to a session for days, so a rollout in
180
+ * an older directory can hold today's records. Only the newest MAX_DAY_DIRS day
181
+ * directories are searched and rollouts untouched for CODEX_MAX_AGE_MS are
182
+ * ignored, which bounds the work on a long-lived ~/.codex.
183
+ */
184
+ function recentRollouts(sessionsDir, now) {
185
+ const rollouts = [];
186
+ for (const dayDir of recentDayDirs(sessionsDir)) {
187
+ for (const name of listNames(dayDir, 'file', ROLLOUT_FILE).slice(0, MAX_ROLLOUTS_PER_DAY)) {
188
+ const file = path.join(dayDir, name);
189
+ try {
190
+ const mtimeMs = fs.statSync(file).mtimeMs;
191
+ if (now - mtimeMs <= CODEX_MAX_AGE_MS)
192
+ rollouts.push({ file, mtimeMs });
193
+ }
194
+ catch {
195
+ // Rollout vanished between listing and stat.
196
+ }
197
+ }
198
+ }
199
+ return rollouts.sort((a, b) => b.mtimeMs - a.mtimeMs).map((r) => r.file);
200
+ }
201
+ /**
202
+ * Codex's latest rate-limit record, or null when there is none to show: no sessions
203
+ * directory, no rollout modified within CODEX_MAX_AGE_MS, or no valid record in the
204
+ * newest MAX_ROLLOUTS_READ of them (a session that has not yet had a model response
205
+ * holds no record, so the next-newest rollout is tried).
206
+ *
207
+ * @param sessionsDir Codex's sessions directory (see codexSessionsDir).
208
+ * @param now Epoch milliseconds, for the rollout age bound.
209
+ */
210
+ export function readCodexUsage(sessionsDir, now) {
211
+ for (const file of recentRollouts(sessionsDir, now).slice(0, MAX_ROLLOUTS_READ)) {
212
+ const usage = latestUsageInFile(file);
213
+ if (usage !== null)
214
+ return usage;
215
+ }
216
+ return null;
217
+ }
218
+ function validateCodexUsage(data) {
219
+ const obj = asRecord(data);
220
+ if (obj === null || !Array.isArray(obj.windows))
221
+ return null;
222
+ const windows = [];
223
+ for (const entry of obj.windows) {
224
+ const raw = asRecord(entry);
225
+ const parsed = raw && makeWindow(raw.minutes, raw.usedPercent, raw.resetsAt);
226
+ if (!parsed)
227
+ return null;
228
+ windows.push(parsed);
229
+ }
230
+ return makeUsage(windows, obj.limitReached);
231
+ }
232
+ /**
233
+ * Codex usage for the statusline: the cached read while it is under 30 s old, else
234
+ * a fresh read of the logs, cached when it found a record. A run that finds none
235
+ * caches nothing and rescans, which is a handful of directory reads.
236
+ */
237
+ export async function gatherCodexUsage(devflowDir, sessionsDir, now = Date.now()) {
238
+ const cacheDir = hudCacheDir(devflowDir);
239
+ const cached = readCache(cacheDir, CODEX_CACHE_KEY, validateCodexUsage);
240
+ if (cached !== null)
241
+ return cached;
242
+ const usage = readCodexUsage(sessionsDir, now);
243
+ if (usage !== null)
244
+ await writeCache(cacheDir, CODEX_CACHE_KEY, usage, CODEX_CACHE_TTL_MS);
245
+ return usage;
246
+ }
247
+ //# sourceMappingURL=codex-usage.js.map
@@ -0,0 +1,37 @@
1
+ import { dim, red } from '../colors.js';
2
+ import { renderQuotaWindow } from './usage-quota.js';
3
+ /** "5h" for 300 minutes, "7d" for 10080; any other length reads as whole days, whole hours, else minutes. */
4
+ function windowLabel(minutes) {
5
+ if (minutes % 1440 === 0)
6
+ return `${minutes / 1440}d`;
7
+ if (minutes % 60 === 0)
8
+ return `${minutes / 60}h`;
9
+ return `${minutes}m`;
10
+ }
11
+ /**
12
+ * Codex usage limits, in usage-quota's style: "Codex 5h ███░░░░░ 40% (1h 2m) · 7d ...".
13
+ *
14
+ * A window whose resets_at has passed has reset since Codex last logged it, so it
15
+ * renders as 0% with no countdown rather than as the stale figure. The red
16
+ * "limited" marker shows while Codex reports a reached limit and at least one
17
+ * window is still counting down; once every window has reset the limit is lifted
18
+ * and the stale marker is dropped.
19
+ */
20
+ export default async function codexQuota(ctx) {
21
+ const usage = ctx.codexUsage;
22
+ if (!usage || usage.windows.length === 0)
23
+ return null;
24
+ const nowMs = Date.now();
25
+ const hasReset = (resetsAt) => resetsAt * 1000 <= nowMs;
26
+ const parts = usage.windows.map((w) => hasReset(w.resetsAt)
27
+ ? renderQuotaWindow(windowLabel(w.minutes), 0, null)
28
+ : renderQuotaWindow(windowLabel(w.minutes), w.usedPercent, w.resetsAt));
29
+ const limited = usage.limitReached !== null && usage.windows.some((w) => !hasReset(w.resetsAt));
30
+ return {
31
+ text: dim('Codex ') +
32
+ parts.map((p) => p.text).join(dim(' · ')) +
33
+ (limited ? ' ' + red('limited') : ''),
34
+ raw: 'Codex ' + parts.map((p) => p.raw).join(' · ') + (limited ? ' limited' : ''),
35
+ };
36
+ }
37
+ //# sourceMappingURL=codex-quota.js.map
@@ -48,7 +48,7 @@ export function formatCountdown(resetsAtEpoch) {
48
48
  return `${totalMinutes}m`;
49
49
  }
50
50
  /** Render a single quota window: "5h ████░░░░ 45% (2h 15m)" */
51
- function renderQuotaWindow(label, percent, resetsAt) {
51
+ export function renderQuotaWindow(label, percent, resetsAt) {
52
52
  const bar = renderBar(Math.round(percent));
53
53
  const countdown = resetsAt != null ? formatCountdown(resetsAt) : '';
54
54
  const countdownText = countdown ? dim(` (${countdown})`) : '';
@@ -18,6 +18,7 @@ export const HUD_COMPONENTS = [
18
18
  'versionBadge',
19
19
  'sessionCost',
20
20
  'usageQuota',
21
+ 'codexQuota',
21
22
  'todoProgress',
22
23
  'configCounts',
23
24
  'learningCounts',
package/dist/hud/index.js CHANGED
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { readStdin } from './stdin.js';
5
5
  import { loadConfig, resolveComponents } from './config.js';
6
6
  import { gatherGitStatus } from './git.js';
7
+ import { codexSessionsDir, gatherCodexUsage } from './codex-usage.js';
7
8
  import { parseTranscript } from './transcript.js';
8
9
  import { persistSessionCost, aggregateCosts } from './cost-history.js';
9
10
  import { gatherConfigCounts } from './components/config-counts.js';
@@ -36,7 +37,10 @@ function extractUsageFromStdin(stdin) {
36
37
  };
37
38
  }
38
39
  async function main() {
39
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT));
40
+ let timer;
41
+ const timeoutPromise = new Promise((_, reject) => {
42
+ timer = setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT);
43
+ });
40
44
  try {
41
45
  const result = await Promise.race([run(), timeoutPromise]);
42
46
  process.stdout.write(result);
@@ -44,6 +48,11 @@ async function main() {
44
48
  catch {
45
49
  // Timeout or error — output nothing (graceful degradation)
46
50
  }
51
+ finally {
52
+ // A live timer would keep node running for the rest of the budget after the
53
+ // line is printed, delaying every status-line render by about 2 s.
54
+ clearTimeout(timer);
55
+ }
47
56
  }
48
57
  async function run() {
49
58
  const stdin = await readStdin();
@@ -56,7 +65,8 @@ async function run() {
56
65
  const components = new Set(resolved);
57
66
  const cwd = stdin.cwd || process.cwd();
58
67
  // D-ONE-HOME: always $HOME/.devflow — no environment variable relocates it.
59
- const devflowDir = path.join(process.env.HOME || homedir(), '.devflow');
68
+ const home = process.env.HOME || homedir();
69
+ const devflowDir = path.join(home, '.devflow');
60
70
  // Determine what data to gather based on enabled components
61
71
  const needsGit = components.has('gitBranch') ||
62
72
  components.has('gitAheadBehind') ||
@@ -67,15 +77,17 @@ async function run() {
67
77
  components.has('configCounts');
68
78
  const needsConfigCounts = components.has('configCounts');
69
79
  const needsLearningCounts = components.has('learningCounts');
80
+ const needsCodexUsage = components.has('codexQuota');
70
81
  const needsSessionCost = components.has('sessionCost');
71
82
  // Parallel data gathering — only fetch what's needed. The learning counts
72
83
  // ride here because resolving the ledger root is one git call.
73
- const [git, transcript, learningCountsData] = await Promise.all([
84
+ const [git, transcript, learningCountsData, codexUsage] = await Promise.all([
74
85
  needsGit ? gatherGitStatus(cwd) : Promise.resolve(null),
75
86
  needsTranscript && stdin.transcript_path
76
87
  ? parseTranscript(stdin.transcript_path)
77
88
  : Promise.resolve(null),
78
89
  needsLearningCounts ? gatherLedgerLearningCounts(cwd) : Promise.resolve(null),
90
+ needsCodexUsage ? gatherCodexUsage(devflowDir, codexSessionsDir(home)) : Promise.resolve(null),
79
91
  ]);
80
92
  // Extract usage quota from stdin rate_limits (replaces OAuth fetch)
81
93
  const usage = components.has('usageQuota') ? extractUsageFromStdin(stdin) : null;
@@ -108,6 +120,7 @@ async function run() {
108
120
  git,
109
121
  transcript,
110
122
  usage,
123
+ codexUsage,
111
124
  configCounts: configCountsData,
112
125
  learningCounts: learningCountsData,
113
126
  costHistory,
@@ -8,6 +8,7 @@ import contextUsage from './components/context-usage.js';
8
8
  import versionBadge from './components/version-badge.js';
9
9
  import sessionDuration from './components/session-duration.js';
10
10
  import usageQuota from './components/usage-quota.js';
11
+ import codexQuota from './components/codex-quota.js';
11
12
  import todoProgress from './components/todo-progress.js';
12
13
  import configCounts from './components/config-counts.js';
13
14
  import learningCounts from './components/learning-counts.js';
@@ -24,6 +25,7 @@ const COMPONENT_MAP = {
24
25
  versionBadge,
25
26
  sessionDuration,
26
27
  usageQuota,
28
+ codexQuota,
27
29
  todoProgress,
28
30
  configCounts,
29
31
  learningCounts,
@@ -37,7 +39,7 @@ const COMPONENT_MAP = {
37
39
  */
38
40
  const LINE_GROUPS = [
39
41
  ['directory', 'gitBranch', 'gitAheadBehind', 'releaseInfo', 'worktreeCount', 'diffStats'],
40
- ['contextUsage', 'usageQuota', 'todoProgress'],
42
+ ['contextUsage', 'usageQuota', 'codexQuota', 'todoProgress'],
41
43
  ['model', 'configCounts', 'sessionCost'],
42
44
  ['learningCounts'],
43
45
  ['versionBadge'],
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: Diagnose
3
- description: Proactive bug finding agent with static+semantic analysis. Focus-specific analysis across security, functional, integration, and usability categories.
2
+ name: Debug
3
+ description: Proactive bug finding agent with static+semantic analysis. Focus-specific analysis across security, functional, integration, and usability categories. The /bug-analysis command spawns it. It is not the /debug command.
4
4
  model: opus
5
5
  effort: medium
6
6
  skills:
@@ -15,7 +15,7 @@ tools:
15
15
  - Skill
16
16
  ---
17
17
 
18
- # Diagnose Agent
18
+ # Debug Agent
19
19
 
20
20
  You are a proactive bug finding agent. Your focus area is specified in the prompt. You hunt for real bugs — not style issues — using a 5-step methodology that combines static analysis findings with semantic code understanding.
21
21
 
@@ -111,7 +111,7 @@ Assign to each verified finding:
111
111
 
112
112
  **Category mapping** (for `/resolve` compatibility — severity-based approximation):
113
113
 
114
- > **Trade-off**: The Review agent uses location-based categories (lines you added / lines you touched / unchanged lines). The Diagnose agent focuses on diff-changed code and lacks per-line location context, so it approximates using severity as a proxy. This means a LOW-severity bug in newly-added code is placed in Pre-existing — not because it predates the change, but to signal lower urgency. The resolve pipeline should treat Pre-existing findings from the Diagnose agent as low-urgency, not as assertions about code origin.
114
+ > **Trade-off**: The Review agent uses location-based categories (lines you added / lines you touched / unchanged lines). The Debug agent focuses on diff-changed code and lacks per-line location context, so it approximates using severity as a proxy. This means a LOW-severity bug in newly-added code is placed in Pre-existing — not because it predates the change, but to signal lower urgency. The resolve pipeline should treat Pre-existing findings from the Debug agent as low-urgency, not as assertions about code origin.
115
115
 
116
116
  - CRITICAL / HIGH severity → `## Issues in Your Changes (BLOCKING)` — must fix before merge
117
117
  - MEDIUM severity → `## Issues in Code You Touched (Should Fix)` — fix while here
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: Code-fix
2
+ name: Fix
3
3
  description: Autonomous gate-failure repair on feature branch. Fixes the listed validation, alignment, QA or CI failures, verifies, and commits.
4
4
  model: sonnet
5
5
  effort: high
@@ -27,7 +27,7 @@ disallowedTools:
27
27
  - DesignSync
28
28
  ---
29
29
 
30
- # Code-fix Agent
30
+ # Fix Agent
31
31
 
32
32
  You are an autonomous repair specialist working on a feature branch. A quality gate (validation, alignment, QA or CI) reported failures. You fix exactly the failures you are given, verify the fix and commit it. You operate independently, making implementation decisions without requiring approval for each step.
33
33
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: Scrutinize
2
+ name: Harden
3
3
  description: Self-review agent that evaluates and fixes implementation issues using 9-pillar framework. Runs in fresh context after Code agent completes.
4
4
  model: opus
5
5
  effort: medium
@@ -30,9 +30,9 @@ disallowedTools:
30
30
  - mcp__*
31
31
  ---
32
32
 
33
- # Scrutinize Agent
33
+ # Harden Agent
34
34
 
35
- You are a meticulous self-review specialist. You evaluate implementations against the 9-pillar quality framework and fix the issues you find. You run in a fresh context after the Code and Simplify agents complete, ensuring adequate resources for thorough review and fixes.
35
+ You are a meticulous self-review specialist. You evaluate implementations against the 9-pillar quality framework and fix the issues you find. You run in a fresh context after the Code and Polish agents complete, ensuring adequate resources for thorough review and fixes.
36
36
 
37
37
  ## Input Context
38
38
 
@@ -53,7 +53,7 @@ You receive from orchestrator:
53
53
 
54
54
  4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
55
55
 
56
- 5. **Evaluate P2** (Documentation): Fix if straightforward. Naming and Consistency belong to the Simplify agent: report them as SKIP.
56
+ 5. **Evaluate P2** (Documentation): Fix if straightforward. Naming and Consistency belong to the Polish agent: report them as SKIP.
57
57
 
58
58
  6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
59
59
 
@@ -91,8 +91,8 @@ Return structured completion status:
91
91
  - Tests: PASS | FIXED (description)
92
92
 
93
93
  ### P2 Pillars
94
- - Naming: SKIP (Simplify agent)
95
- - Consistency: SKIP (Simplify agent)
94
+ - Naming: SKIP (Polish agent)
95
+ - Consistency: SKIP (Polish agent)
96
96
  - Documentation: PASS | FIXED (description)
97
97
 
98
98
  ### Files Modified
@@ -1,6 +1,6 @@
1
1
  ---
2
- name: Code-edit
3
- description: Autonomous mechanical edit on feature branch. Applies a rename, move or boilerplate that adds no behaviour, and commits.
2
+ name: Refactor
3
+ description: Autonomous mechanical edit on feature branch. Makes a given rename, move or boilerplate change and commits. For judgment-based cleanup use Polish.
4
4
  model: sonnet
5
5
  effort: high
6
6
  skills:
@@ -26,7 +26,7 @@ disallowedTools:
26
26
  - DesignSync
27
27
  ---
28
28
 
29
- # Code-edit Agent
29
+ # Refactor Agent
30
30
 
31
31
  You are an autonomous editing specialist working on a feature branch. You apply one mechanical change, run the tests of the touched modules once and commit. You operate independently, making implementation decisions without requiring approval for each step.
32
32