devflow-kit 3.4.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.
- package/CHANGELOG.md +127 -0
- package/README.md +12 -8
- package/dist/agents/code.md +39 -182
- package/dist/agents/{diagnose.md → debug.md} +4 -4
- package/dist/agents/design.md +0 -1
- package/dist/agents/fix.md +165 -0
- package/dist/agents/git.md +31 -693
- package/dist/agents/{scrutinize.md → harden.md} +9 -6
- package/dist/agents/knowledge.md +8 -2
- package/dist/agents/refactor.md +133 -0
- package/dist/agents/research.md +6 -0
- package/dist/agents/review.md +0 -4
- package/dist/agents/skim.md +4 -0
- package/dist/agents/triage.md +8 -1
- package/dist/cli/agents-view/render.js +1 -1
- package/dist/cli/commands/behavior-prompts.js +77 -0
- package/dist/cli/commands/behavior.js +123 -0
- package/dist/cli/commands/flags.js +8 -2
- package/dist/cli/commands/init-seed.js +7 -3
- package/dist/cli/commands/init.js +75 -8
- package/dist/cli/commands/learning.js +12 -12
- package/dist/cli/commands/legacy-hooks.js +1 -1
- package/dist/cli/commands/rules.js +16 -0
- package/dist/cli/commands/tracker.js +112 -15
- package/dist/cli/commands/uninstall.js +111 -5
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +22 -18
- package/dist/commands/code-review.md +25 -29
- package/dist/commands/debug.md +36 -15
- package/dist/commands/dynamic-build.md +64 -78
- package/dist/commands/dynamic-plan.md +18 -14
- package/dist/commands/dynamic-profile.md +8 -6
- package/dist/commands/dynamic-tickets.md +19 -15
- package/dist/commands/explore.md +14 -14
- package/dist/commands/implement.md +122 -225
- package/dist/commands/plan.md +32 -47
- package/dist/commands/release.md +12 -7
- package/dist/commands/research.md +26 -15
- package/dist/commands/resolve.md +53 -92
- package/dist/commands/self-review.md +42 -42
- package/dist/core/agent-frontmatter.js +101 -0
- package/dist/core/agent-models.js +47 -11
- package/dist/core/compliance-compose.js +6 -4
- package/dist/core/{learning-tuning-config.js → curate-tuning-config.js} +14 -14
- package/dist/core/feature-switch.js +23 -2
- package/dist/core/flags.js +111 -0
- package/dist/core/learning-queue-cleanup.js +1 -1
- package/dist/core/manifest.js +11 -3
- package/dist/core/mds-variants.js +68 -26
- package/dist/core/migrations.js +63 -44
- package/dist/core/plugins.js +49 -70
- package/dist/core/project-paths.js +3 -7
- package/dist/core/proxy-log.js +1 -1
- package/dist/core/proxy-state.js +1 -1
- package/dist/core/tracker.js +120 -2
- package/dist/hud/codex-usage.js +247 -0
- package/dist/hud/components/codex-quota.js +37 -0
- package/dist/hud/components/usage-quota.js +1 -1
- package/dist/hud/config.js +1 -0
- package/dist/hud/index.js +16 -3
- package/dist/hud/render.js +3 -1
- package/dist/learning-off/agents/code.md +37 -180
- package/dist/learning-off/agents/{diagnose.md → debug.md} +4 -4
- package/dist/learning-off/agents/design.md +0 -1
- package/dist/learning-off/agents/fix.md +161 -0
- package/dist/learning-off/agents/{scrutinize.md → harden.md} +9 -6
- package/dist/learning-off/agents/knowledge.md +8 -2
- package/dist/learning-off/agents/refactor.md +129 -0
- package/dist/learning-off/agents/research.md +6 -0
- package/dist/learning-off/agents/review.md +0 -4
- package/dist/learning-off/agents/skim.md +4 -0
- package/dist/learning-off/agents/triage.md +8 -1
- package/dist/learning-off/commands/bug-analysis.md +12 -12
- package/dist/learning-off/commands/code-review.md +14 -22
- package/dist/learning-off/commands/debug.md +24 -8
- package/dist/learning-off/commands/dynamic-build.md +58 -74
- package/dist/learning-off/commands/dynamic-plan.md +12 -10
- package/dist/learning-off/commands/dynamic-profile.md +2 -2
- package/dist/learning-off/commands/dynamic-tickets.md +13 -11
- package/dist/learning-off/commands/explore.md +3 -7
- package/dist/learning-off/commands/implement.md +110 -217
- package/dist/learning-off/commands/plan.md +21 -39
- package/dist/learning-off/commands/release.md +7 -4
- package/dist/learning-off/commands/research.md +16 -9
- package/dist/learning-off/commands/resolve.md +42 -85
- package/dist/learning-off/commands/self-review.md +30 -34
- package/dist/skills/git/references/learn-conventions.md +27 -3
- package/dist/skills/git/references/pr/check-ci-status.md +19 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +21 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +30 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +18 -0
- package/dist/skills/git/references/pr/post-review-summary.md +20 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +28 -4
- package/dist/skills/git/references/pr/update-pr-evidence.md +17 -6
- package/dist/skills/git/references/pr/validate-branch.md +26 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +24 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/github/create-release.md +30 -1
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/github/fetch-issue.md +36 -1
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +47 -1
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +25 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/github/post-wave-report.md +15 -1
- package/dist/skills/git/references/tracker/github/setup-task.md +41 -1
- package/dist/skills/git/references/tracker/jira/associate-release.md +24 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/jira/create-release.md +30 -1
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +36 -1
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +47 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +25 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +15 -1
- package/dist/skills/git/references/tracker/jira/setup-task.md +41 -1
- package/dist/skills/git/references/tracker/linear/associate-release.md +24 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/linear/create-release.md +30 -1
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +36 -1
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +47 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +25 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +15 -1
- package/dist/skills/git/references/tracker/linear/setup-task.md +41 -1
- package/dist/targets/claude-code/agent-type-owner.js +164 -0
- package/dist/targets/claude-code/behavior-install.js +111 -0
- package/dist/targets/claude-code/installer.js +177 -77
- package/dist/targets/claude-code/learning-install.js +9 -1
- package/dist/targets/claude-code/tracker-install.js +33 -16
- package/dist/targets/claude-code/tracker-servers.js +107 -0
- package/package.json +4 -3
- package/src/assets/agents/_partials/_code.mds +109 -0
- package/src/assets/agents/code.mds +18 -222
- package/src/assets/agents/{learning.md → curate.md} +22 -72
- package/src/assets/agents/{diagnose.mds → debug.mds} +4 -4
- package/src/assets/agents/design.mds +0 -1
- package/src/assets/agents/evaluate.md +11 -5
- package/src/assets/agents/explore.md +58 -0
- package/src/assets/agents/fix.mds +113 -0
- package/src/assets/agents/git.mds +31 -693
- package/src/assets/agents/{scrutinize.mds → harden.mds} +9 -6
- package/src/assets/agents/knowledge.mds +8 -2
- package/src/assets/agents/{simplify.md → polish.md} +7 -4
- package/src/assets/agents/refactor.mds +81 -0
- package/src/assets/agents/research.mds +6 -0
- package/src/assets/agents/review.mds +0 -4
- package/src/assets/agents/skim.mds +4 -0
- package/src/assets/agents/synthesize.md +26 -352
- package/src/assets/agents/test.md +10 -2
- package/src/assets/agents/tracker.md +20 -7
- package/src/assets/agents/triage.mds +8 -1
- package/src/assets/agents/validate.md +5 -0
- package/src/assets/commands/_partials/_compliance.mds +36 -0
- package/src/assets/commands/_partials/_decisions.mds +26 -6
- package/src/assets/commands/_partials/_engine.mds +17 -17
- package/src/assets/commands/_partials/_evidence_policy.mds +0 -17
- package/src/assets/commands/_partials/_knowledge.mds +14 -9
- package/src/assets/commands/_partials/_preamble.mds +5 -3
- package/src/assets/commands/_partials/_roster.mds +6 -5
- package/src/assets/commands/_partials/_wave.mds +2 -2
- package/src/assets/commands/bug-analysis.mds +10 -10
- package/src/assets/commands/code-review.mds +12 -22
- package/src/assets/commands/debug.mds +24 -2
- package/src/assets/commands/dynamic-build.mds +34 -51
- package/src/assets/commands/dynamic-plan.mds +4 -3
- package/src/assets/commands/dynamic-tickets.mds +5 -4
- package/src/assets/commands/explore.mds +1 -1
- package/src/assets/commands/implement.mds +108 -201
- package/src/assets/commands/plan.mds +19 -44
- package/src/assets/commands/release.mds +8 -3
- package/src/assets/commands/research.mds +14 -7
- package/src/assets/commands/resolve.mds +37 -82
- package/src/assets/commands/self-review.mds +27 -27
- package/src/assets/mds/.mdsroot +4 -0
- package/src/assets/mds/git/_ops.mds +383 -0
- package/src/assets/mds/git/_ops_pr.mds +201 -0
- package/src/assets/mds/git/_ops_steps.mds +147 -0
- package/src/assets/mds/git/_pr.mds +45 -50
- package/src/assets/mds/git/_references.mds +6 -3
- package/src/assets/mds/tracker/_common.mds +5 -5
- package/src/assets/mds/tracker/_contract.mds +4 -3
- package/src/assets/mds/tracker/_github.mds +60 -14
- package/src/assets/mds/tracker/_jira.mds +60 -14
- package/src/assets/mds/tracker/_linear.mds +60 -14
- package/src/assets/mds/tracker/_steps.mds +10 -9
- package/src/assets/rules/behavior.md +6 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -3
- package/src/assets/scripts/hooks/capture-prompt +1 -1
- package/src/assets/scripts/hooks/capture-question +1 -1
- package/src/assets/scripts/hooks/capture-turn +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +2 -2
- package/src/assets/scripts/hooks/lib/learning-store.cjs +1 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +4 -10
- package/src/assets/scripts/hooks/preamble +1 -1
- package/src/assets/scripts/hooks/session-start-context +58 -45
- package/src/assets/scripts/verify-evidence.cjs +726 -9
- package/src/assets/skills/agent-references/SKILL.md +37 -0
- package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +35 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +15 -0
- package/src/assets/skills/agent-references/references/curate/maintain.md +71 -0
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +71 -0
- package/src/assets/skills/agent-references/references/synthesize/design.md +36 -0
- package/src/assets/skills/agent-references/references/synthesize/exploration.md +38 -0
- package/src/assets/skills/agent-references/references/synthesize/planning.md +60 -0
- package/src/assets/skills/agent-references/references/synthesize/research.md +61 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +76 -0
- package/src/assets/skills/apply-decisions/SKILL.md +6 -4
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +81 -84
- package/src/assets/skills/docs-framework/references/patterns.md +14 -25
- package/src/assets/skills/docs-framework/references/violations.md +7 -7
- package/src/assets/skills/gap-analysis/SKILL.md +0 -16
- package/src/assets/skills/gap-analysis/references/compliance.md +19 -0
- package/src/assets/skills/qa/SKILL.md +1 -1
- package/src/assets/skills/quality-gates/SKILL.md +7 -7
- package/src/assets/skills/quality-gates/references/report-template.md +7 -7
- package/dist/skills/git/references/pr/ensure-pr-ready.md +0 -24
package/dist/core/tracker.js
CHANGED
|
@@ -60,6 +60,44 @@ export const TRACKER_PROVIDER_IDS = TRACKER_PROVIDERS.map(p => p.id);
|
|
|
60
60
|
*/
|
|
61
61
|
export const DEFAULT_TRACKER_PROVIDER = 'github';
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
|
+
// Tracker tool servers — the allowlist half of the Git and Tracker agents
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
/**
|
|
66
|
+
* D-TRACKER-SERVER-ALLOWLIST: the Git and Tracker agents hold a tool ALLOWLIST, and the
|
|
67
|
+
* tracker tool servers a user has connected differ per machine, so the shipped allowlist
|
|
68
|
+
* names no server (the agent sources stay vendor-neutral). The installer stamps one
|
|
69
|
+
* whole-server entry per name below onto the INSTALLED `tools:` line of those two agents
|
|
70
|
+
* (`stampAgentTools`, src/core/agent-frontmatter.ts), after the file is copied. Claude Code
|
|
71
|
+
* has no pattern form for an allowlist entry, so a server is granted by its name; a name
|
|
72
|
+
* that matches no connected server is inert (it grants nothing and blocks nothing).
|
|
73
|
+
*
|
|
74
|
+
* The default is one FLAT list, stamped whatever the provider: every install carries every
|
|
75
|
+
* provider, and a repository may select a provider other than the machine's, so a list
|
|
76
|
+
* keyed on the machine's provider would strand that repository. A server the user runs
|
|
77
|
+
* under another name is added with `devflow tracker --servers`. A wrong or missing name
|
|
78
|
+
* does not fail silently: the agent finds no tool for the capability it needs and reports
|
|
79
|
+
* `TRACEABILITY: DEGRADED (no tracker tool for {capability})`.
|
|
80
|
+
*
|
|
81
|
+
* The default leaves `github` out. Under the github provider the Git agent uses `gh` and
|
|
82
|
+
* never reads `_mcp.md`, so a stamped `github` server is never used by a tracker operation;
|
|
83
|
+
* under jira or linear, a connected GitHub server that also advertises issue creation would
|
|
84
|
+
* make the `_mcp.md` capability selection ambiguous, which is a degraded run. A user who
|
|
85
|
+
* wants it adds it with `devflow tracker --servers`.
|
|
86
|
+
*/
|
|
87
|
+
export const TRACKER_MCP_SERVERS_DEFAULT = Object.freeze([
|
|
88
|
+
'atlassian',
|
|
89
|
+
'jira',
|
|
90
|
+
'claude_ai_Atlassian',
|
|
91
|
+
'linear',
|
|
92
|
+
'claude_ai_Linear',
|
|
93
|
+
]);
|
|
94
|
+
/** A server name as a user may configure it. Byte-exact, like a provider token. */
|
|
95
|
+
export const TRACKER_SERVER_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
96
|
+
/** The most servers one list may hold; every entry lands in a prompt-adjacent frontmatter line. */
|
|
97
|
+
export const TRACKER_SERVERS_MAX = 32;
|
|
98
|
+
/** The reserved word that returns the setting to {@link TRACKER_MCP_SERVERS_DEFAULT}. */
|
|
99
|
+
export const TRACKER_SERVERS_DEFAULT_WORD = 'default';
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
63
101
|
// Artifact basenames — one spelling for every TypeScript reader
|
|
64
102
|
//
|
|
65
103
|
// NOT the only spelling in the repository, and a rename that assumes it is will
|
|
@@ -238,6 +276,72 @@ export function parseTrackerId(input) {
|
|
|
238
276
|
}
|
|
239
277
|
return { ok: true, value: input };
|
|
240
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Validate a list of tracker server names. The one rule set behind both the CLI argument
|
|
281
|
+
* and the manifest field.
|
|
282
|
+
*
|
|
283
|
+
* REJECT, NEVER REPAIR (as {@link parseTrackerId}): a name is accepted byte-exact against
|
|
284
|
+
* {@link TRACKER_SERVER_NAME_RE} and must not hold `__`, which is what separates a server
|
|
285
|
+
* from its tool in an entry such as `mcp__server__tool`. No trim, no case folding, no
|
|
286
|
+
* alias. Exact duplicates collapse to the first occurrence and the order is kept. The list
|
|
287
|
+
* is non-empty and holds at most {@link TRACKER_SERVERS_MAX} distinct names.
|
|
288
|
+
*/
|
|
289
|
+
export function validateTrackerServers(raw) {
|
|
290
|
+
if (raw.length === 0) {
|
|
291
|
+
return { ok: false, error: 'No tracker servers given. Pass one or more server names, or "default".' };
|
|
292
|
+
}
|
|
293
|
+
const seen = new Set();
|
|
294
|
+
for (const entry of raw) {
|
|
295
|
+
if (typeof entry !== 'string' || !TRACKER_SERVER_NAME_RE.test(entry)) {
|
|
296
|
+
return {
|
|
297
|
+
ok: false,
|
|
298
|
+
error: `Invalid tracker server name: "${describeTrackerValue(entry)}". ` +
|
|
299
|
+
'Names are 1-64 characters from A-Z, a-z, 0-9, "_" and "-".',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (entry.includes('__')) {
|
|
303
|
+
return {
|
|
304
|
+
ok: false,
|
|
305
|
+
error: `Invalid tracker server name: "${describeTrackerValue(entry)}". ` +
|
|
306
|
+
'A name must not contain "__" (it separates a server from its tool).',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
seen.add(entry);
|
|
310
|
+
}
|
|
311
|
+
if (seen.size > TRACKER_SERVERS_MAX) {
|
|
312
|
+
return { ok: false, error: `Too many tracker servers: ${seen.size} (at most ${TRACKER_SERVERS_MAX}).` };
|
|
313
|
+
}
|
|
314
|
+
return { ok: true, value: [...seen] };
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Strict boundary parser for `devflow tracker --servers <list>`.
|
|
318
|
+
*
|
|
319
|
+
* `default` alone selects {@link TRACKER_MCP_SERVERS_DEFAULT}; anything else is a
|
|
320
|
+
* comma-separated list of names handed to {@link validateTrackerServers} unchanged. An
|
|
321
|
+
* empty argument, an empty item (`a,,b`, a trailing comma) and the reserved word inside a
|
|
322
|
+
* list are errors, never silently dropped.
|
|
323
|
+
*/
|
|
324
|
+
export function parseTrackerServers(input) {
|
|
325
|
+
if (input === '') {
|
|
326
|
+
return { ok: false, error: 'Missing tracker servers. Pass a comma-separated list of names, or "default".' };
|
|
327
|
+
}
|
|
328
|
+
if (input === TRACKER_SERVERS_DEFAULT_WORD)
|
|
329
|
+
return { ok: true, value: { kind: 'default' } };
|
|
330
|
+
const items = input.split(',');
|
|
331
|
+
if (items.includes(TRACKER_SERVERS_DEFAULT_WORD)) {
|
|
332
|
+
return { ok: false, error: `"${TRACKER_SERVERS_DEFAULT_WORD}" is reserved: pass it alone, not inside a list.` };
|
|
333
|
+
}
|
|
334
|
+
const validated = validateTrackerServers(items);
|
|
335
|
+
return validated.ok ? { ok: true, value: { kind: 'list', servers: validated.value } } : validated;
|
|
336
|
+
}
|
|
337
|
+
/** The servers in force for `state`: its own list, else the default. */
|
|
338
|
+
export function effectiveTrackerServers(state) {
|
|
339
|
+
return state.servers ?? TRACKER_MCP_SERVERS_DEFAULT;
|
|
340
|
+
}
|
|
341
|
+
/** The whole-server allowlist entries for `servers`, in order: `github` becomes `mcp__github`. */
|
|
342
|
+
export function trackerServerTools(servers) {
|
|
343
|
+
return servers.map(name => `mcp__${name}`);
|
|
344
|
+
}
|
|
241
345
|
/**
|
|
242
346
|
* Tolerant sink normaliser for a raw `manifest.features.tracker` value.
|
|
243
347
|
*
|
|
@@ -249,9 +353,11 @@ export function parseTrackerId(input) {
|
|
|
249
353
|
* D-TRACKER-SELF-HEAL [DR-26]: self-healing here is SILENT and emits no
|
|
250
354
|
* DEGRADED — that is the correct self-heal behaviour, and it is a different
|
|
251
355
|
* condition from a per-repo config value outside the domain (which does emit
|
|
252
|
-
* `unknown tracker provider`). The two must not be conflated.
|
|
356
|
+
* `unknown tracker provider`). The two must not be conflated. The one exception is
|
|
357
|
+
* `servers` (see the body): a present but invalid list heals to the default AND reaches
|
|
358
|
+
* `onWarning` when a caller supplies one, because the user wrote that list on purpose.
|
|
253
359
|
*/
|
|
254
|
-
export function normalizeTrackerFeature(raw) {
|
|
360
|
+
export function normalizeTrackerFeature(raw, onWarning) {
|
|
255
361
|
const DEFAULT = { provider: DEFAULT_TRACKER_PROVIDER };
|
|
256
362
|
if (raw === null || raw === undefined || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
257
363
|
return DEFAULT;
|
|
@@ -259,6 +365,18 @@ export function normalizeTrackerFeature(raw) {
|
|
|
259
365
|
const obj = raw;
|
|
260
366
|
if (!isTrackerProvider(obj.provider))
|
|
261
367
|
return DEFAULT;
|
|
368
|
+
// `servers`: absent is the default, silently. A present value that is not a valid list is
|
|
369
|
+
// dropped WHOLE (never trimmed to its valid names) and reported through `onWarning`, which a
|
|
370
|
+
// caller with a terminal passes; the reader itself has no output channel. The state then
|
|
371
|
+
// reads as the default list, and the next `devflow tracker --servers` or `devflow init`
|
|
372
|
+
// writes the manifest without the bad value.
|
|
373
|
+
if (obj.servers === undefined)
|
|
374
|
+
return { provider: obj.provider };
|
|
375
|
+
const servers = Array.isArray(obj.servers) ? validateTrackerServers(obj.servers) : undefined;
|
|
376
|
+
if (servers?.ok === true)
|
|
377
|
+
return { provider: obj.provider, servers: servers.value };
|
|
378
|
+
onWarning?.(`The manifest's tracker servers setting is not a valid list (${servers?.ok === false ? servers.error : 'not a list'}); ` +
|
|
379
|
+
'using the default. Run `devflow tracker --servers <names>` to set it, or `devflow tracker --servers default`.');
|
|
262
380
|
return { provider: obj.provider };
|
|
263
381
|
}
|
|
264
382
|
// ---------------------------------------------------------------------------
|
|
@@ -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})`) : '';
|
package/dist/hud/config.js
CHANGED
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
|
-
|
|
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
|
|
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,
|
package/dist/hud/render.js
CHANGED
|
@@ -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'],
|