deuk-agent-flow 4.2.7 → 5.0.4

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 (170) hide show
  1. package/CHANGELOG.ko.md +277 -0
  2. package/CHANGELOG.md +794 -124
  3. package/LICENSE +0 -0
  4. package/README.ko.md +97 -17
  5. package/README.md +108 -25
  6. package/bin/deuk-agent-flow.js +11 -13
  7. package/bin/deuk-agent-rule.js +1 -1
  8. package/bundled/README.md +3 -0
  9. package/bundled/deuk-agent-flow.vsix +0 -0
  10. package/core-rules/AGENTS.md +30 -120
  11. package/docs/architecture.ko.md +155 -2
  12. package/docs/architecture.md +155 -2
  13. package/docs/assets/agentflow-panel-skills.png +0 -0
  14. package/docs/assets/agentflow-panel.png +0 -0
  15. package/docs/how-it-works.ko.md +109 -52
  16. package/docs/how-it-works.md +128 -71
  17. package/docs/principles.ko.md +68 -68
  18. package/docs/principles.md +68 -68
  19. package/docs/usage-guide.ko.md +251 -212
  20. package/package.json +60 -45
  21. package/scripts/bundle-vscode-vsix.ts +67 -0
  22. package/scripts/{cli-args.mjs → cli-args.ts} +49 -12
  23. package/scripts/cli-context-commands.ts +35 -0
  24. package/scripts/cli-init-claude.ts +179 -0
  25. package/scripts/cli-init-commands.ts +99 -0
  26. package/scripts/cli-init-core.ts +89 -0
  27. package/scripts/cli-init-instructions.ts +489 -0
  28. package/scripts/cli-init-logic.ts +46 -0
  29. package/scripts/cli-init-migrate.ts +504 -0
  30. package/scripts/cli-init-workspace.ts +63 -0
  31. package/scripts/cli-locales.ts +85 -0
  32. package/scripts/{cli-prompts.mjs → cli-prompts.ts} +19 -34
  33. package/scripts/{cli-rule-compiler.mjs → cli-rule-compiler.ts} +128 -112
  34. package/scripts/cli-skill-commands.ts +709 -0
  35. package/scripts/{cli-telemetry-commands.mjs → cli-telemetry-commands.ts} +25 -22
  36. package/scripts/cli-ticket-command-shared.ts +4 -0
  37. package/scripts/cli-ticket-commands.ts +3781 -0
  38. package/scripts/cli-ticket-docmeta.ts +103 -0
  39. package/scripts/cli-ticket-document.ts +153 -0
  40. package/scripts/cli-ticket-evidence.ts +312 -0
  41. package/scripts/cli-ticket-home.ts +449 -0
  42. package/scripts/cli-ticket-index.ts +283 -0
  43. package/scripts/{cli-ticket-migration.mjs → cli-ticket-migration.ts} +44 -68
  44. package/scripts/cli-ticket-parser.ts +100 -0
  45. package/scripts/cli-ticket-scan.ts +102 -0
  46. package/scripts/cli-ticket-surface.ts +56 -0
  47. package/scripts/{cli-usage-commands.mjs → cli-usage-commands.ts} +325 -326
  48. package/scripts/cli-utils.ts +1560 -0
  49. package/scripts/cli.ts +695 -0
  50. package/scripts/{lint-md.mjs → lint-md.ts} +32 -42
  51. package/scripts/lint-rules.ts +203 -0
  52. package/scripts/{merge-logic.mjs → merge-logic.ts} +44 -44
  53. package/scripts/oss-prepack.js +32 -0
  54. package/scripts/out/scripts/bundle-vscode-vsix.js +68 -0
  55. package/scripts/out/scripts/bundle-vscode-vsix.js.map +1 -0
  56. package/scripts/out/scripts/cli-args.js +374 -0
  57. package/scripts/out/scripts/cli-args.js.map +1 -0
  58. package/scripts/out/scripts/cli-context-commands.js +31 -0
  59. package/scripts/out/scripts/cli-context-commands.js.map +1 -0
  60. package/scripts/out/scripts/cli-init-claude.js +176 -0
  61. package/scripts/out/scripts/cli-init-claude.js.map +1 -0
  62. package/scripts/out/scripts/cli-init-commands.js +87 -0
  63. package/scripts/out/scripts/cli-init-commands.js.map +1 -0
  64. package/scripts/out/scripts/cli-init-core.js +89 -0
  65. package/scripts/out/scripts/cli-init-core.js.map +1 -0
  66. package/scripts/out/scripts/cli-init-instructions.js +460 -0
  67. package/scripts/out/scripts/cli-init-instructions.js.map +1 -0
  68. package/scripts/out/scripts/cli-init-logic.js +41 -0
  69. package/scripts/out/scripts/cli-init-logic.js.map +1 -0
  70. package/scripts/out/scripts/cli-init-migrate.js +497 -0
  71. package/scripts/out/scripts/cli-init-migrate.js.map +1 -0
  72. package/scripts/out/scripts/cli-init-workspace.js +57 -0
  73. package/scripts/out/scripts/cli-init-workspace.js.map +1 -0
  74. package/scripts/out/scripts/cli-locales.js +83 -0
  75. package/scripts/out/scripts/cli-locales.js.map +1 -0
  76. package/scripts/out/scripts/cli-prompts.js +101 -0
  77. package/scripts/out/scripts/cli-prompts.js.map +1 -0
  78. package/scripts/out/scripts/cli-rule-compiler.js +121 -0
  79. package/scripts/out/scripts/cli-rule-compiler.js.map +1 -0
  80. package/scripts/out/scripts/cli-skill-commands.js +708 -0
  81. package/scripts/out/scripts/cli-skill-commands.js.map +1 -0
  82. package/scripts/out/scripts/cli-telemetry-commands.js +604 -0
  83. package/scripts/out/scripts/cli-telemetry-commands.js.map +1 -0
  84. package/scripts/out/scripts/cli-ticket-command-shared.js +2 -0
  85. package/scripts/out/scripts/cli-ticket-command-shared.js.map +1 -0
  86. package/scripts/out/scripts/cli-ticket-commands.js +3530 -0
  87. package/scripts/out/scripts/cli-ticket-commands.js.map +1 -0
  88. package/scripts/out/scripts/cli-ticket-docmeta.js +90 -0
  89. package/scripts/out/scripts/cli-ticket-docmeta.js.map +1 -0
  90. package/scripts/out/scripts/cli-ticket-document.js +121 -0
  91. package/scripts/out/scripts/cli-ticket-document.js.map +1 -0
  92. package/scripts/out/scripts/cli-ticket-evidence.js +286 -0
  93. package/scripts/out/scripts/cli-ticket-evidence.js.map +1 -0
  94. package/scripts/out/scripts/cli-ticket-home.js +440 -0
  95. package/scripts/out/scripts/cli-ticket-home.js.map +1 -0
  96. package/scripts/out/scripts/cli-ticket-index.js +272 -0
  97. package/scripts/out/scripts/cli-ticket-index.js.map +1 -0
  98. package/scripts/out/scripts/cli-ticket-migration.js +261 -0
  99. package/scripts/out/scripts/cli-ticket-migration.js.map +1 -0
  100. package/scripts/out/scripts/cli-ticket-parser.js +90 -0
  101. package/scripts/out/scripts/cli-ticket-parser.js.map +1 -0
  102. package/scripts/out/scripts/cli-ticket-scan.js +93 -0
  103. package/scripts/out/scripts/cli-ticket-scan.js.map +1 -0
  104. package/scripts/out/scripts/cli-ticket-surface.js +53 -0
  105. package/scripts/out/scripts/cli-ticket-surface.js.map +1 -0
  106. package/scripts/out/scripts/cli-usage-commands.js +310 -0
  107. package/scripts/out/scripts/cli-usage-commands.js.map +1 -0
  108. package/scripts/out/scripts/cli-utils.js +1544 -0
  109. package/scripts/out/scripts/cli-utils.js.map +1 -0
  110. package/scripts/out/scripts/cli.js +696 -0
  111. package/scripts/out/scripts/cli.js.map +1 -0
  112. package/scripts/out/scripts/lint-md.js +238 -0
  113. package/scripts/out/scripts/lint-md.js.map +1 -0
  114. package/scripts/out/scripts/lint-rules.js +193 -0
  115. package/scripts/out/scripts/lint-rules.js.map +1 -0
  116. package/scripts/out/scripts/merge-logic.js +38 -0
  117. package/scripts/out/scripts/merge-logic.js.map +1 -0
  118. package/scripts/out/scripts/plan-parser.js +52 -0
  119. package/scripts/out/scripts/plan-parser.js.map +1 -0
  120. package/scripts/out/scripts/ticket-machine.js +94 -0
  121. package/scripts/out/scripts/ticket-machine.js.map +1 -0
  122. package/scripts/out/scripts/ticket-workflow.js +498 -0
  123. package/scripts/out/scripts/ticket-workflow.js.map +1 -0
  124. package/scripts/{plan-parser.mjs → plan-parser.ts} +53 -53
  125. package/scripts/ticket-machine.ts +99 -0
  126. package/scripts/ticket-workflow.ts +544 -0
  127. package/templates/MODULE_RULE_TEMPLATE.md +11 -11
  128. package/templates/PROJECT_RULE.md +46 -47
  129. package/templates/TICKET_TEMPLATE.ko.md +48 -44
  130. package/templates/TICKET_TEMPLATE.md +48 -44
  131. package/templates/project-memory.md +19 -0
  132. package/templates/project-pilot/CONFORMANCE_GATE_TEMPLATE.md +25 -25
  133. package/templates/project-pilot/DRIFT_CHECKLIST.md +27 -27
  134. package/templates/project-pilot/FLOW_CONTRACT_TEMPLATE.md +26 -26
  135. package/templates/project-pilot/IMPLEMENTATION_MATRIX_TEMPLATE.md +30 -30
  136. package/templates/project-pilot/INTEGRATION_CONTRACT_TEMPLATE.md +26 -26
  137. package/templates/project-pilot/OWNER_MAP_TEMPLATE.md +15 -15
  138. package/templates/project-pilot/PROJECT_PILOT_RULE_TEMPLATE.md +34 -34
  139. package/templates/project-pilot/REFACTOR_CONTRACT_TEMPLATE.md +32 -32
  140. package/templates/project-pilot/REMEDIATION_PLAN_TEMPLATE.md +33 -33
  141. package/templates/rules.d/deukcontext-mcp.md +31 -31
  142. package/templates/rules.d/platform-coexistence.md +29 -29
  143. package/templates/skills/context-recall/SKILL.md +3 -1
  144. package/templates/skills/doc-sync/SKILL.md +111 -0
  145. package/templates/skills/generated-file-guard/SKILL.md +3 -1
  146. package/templates/skills/persona-maid/SKILL.md +65 -0
  147. package/templates/skills/project-pilot/SKILL.md +14 -63
  148. package/templates/skills/role-closer/SKILL.md +27 -0
  149. package/templates/skills/role-implementer/SKILL.md +30 -0
  150. package/templates/skills/role-planner/SKILL.md +29 -0
  151. package/templates/skills/role-qa-verifier/SKILL.md +30 -0
  152. package/templates/skills/safe-refactor/SKILL.md +3 -1
  153. package/templates/skills/ticket-status-surface/SKILL.md +17 -0
  154. package/NOTICE.md +0 -19
  155. package/core-rules/GEMINI.md +0 -7
  156. package/docs/npm-publish-guide.ko.md +0 -70
  157. package/scripts/cli-init-commands.mjs +0 -1759
  158. package/scripts/cli-init-logic.mjs +0 -64
  159. package/scripts/cli-skill-commands.mjs +0 -212
  160. package/scripts/cli-ticket-command-shared.mjs +0 -60
  161. package/scripts/cli-ticket-commands.mjs +0 -2474
  162. package/scripts/cli-ticket-index.mjs +0 -322
  163. package/scripts/cli-ticket-parser.mjs +0 -210
  164. package/scripts/cli-utils.mjs +0 -602
  165. package/scripts/cli.mjs +0 -256
  166. package/scripts/lint-rules.mjs +0 -197
  167. package/scripts/publish-dual-npm.mjs +0 -141
  168. package/scripts/smoke-npm-docker.mjs +0 -102
  169. package/scripts/smoke-npm-local.mjs +0 -110
  170. package/scripts/update-download-badge.mjs +0 -103
@@ -1,326 +1,325 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
- import { join } from "path";
3
- import { AGENT_ROOT_DIR } from "./cli-utils.mjs";
4
-
5
- const USAGE_FILENAME = `${AGENT_ROOT_DIR}/usage.json`;
6
- const USAGE_VERSION = 1;
7
- const VALID_TASK_GRADES = new Set(["S", "A", "B", "C"]);
8
- const DEFAULT_PLATFORM = "codex";
9
- const PLATFORM_ALIASES = {
10
- codex: "codex",
11
- copilot: "copilot",
12
- ghcopilot: "copilot",
13
- "github-copilot": "copilot",
14
- claude: "claude",
15
- claudecode: "claude",
16
- cursor: "cursor"
17
- };
18
- const PLATFORM_DEFAULT_CLIENT = {
19
- codex: "Codex",
20
- copilot: "Copilot",
21
- claude: "ClaudeCode",
22
- cursor: "Cursor"
23
- };
24
-
25
- export async function runUsage(action, opts) {
26
- if (action === "set") {
27
- return runUsageSet(opts);
28
- }
29
- if (action === "status") {
30
- return runUsageStatus(opts);
31
- }
32
- if (action === "advise") {
33
- return runUsageAdvise(opts);
34
- }
35
- console.error("Unknown usage action: " + action);
36
- console.log("Usage: npx deuk-agent-flow usage <set|status|advise> [options]");
37
- }
38
-
39
- export function loadUsageState(cwd) {
40
- const absPath = join(cwd, USAGE_FILENAME);
41
- if (!existsSync(absPath)) return null;
42
- try {
43
- const data = JSON.parse(readFileSync(absPath, "utf8"));
44
- if (data.version !== USAGE_VERSION) return null;
45
- return normalizeUsageState(data);
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
- function writeUsageState(cwd, state) {
52
- const absPath = join(cwd, USAGE_FILENAME);
53
- const dir = join(cwd, AGENT_ROOT_DIR);
54
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
55
- writeFileSync(absPath, JSON.stringify({
56
- version: USAGE_VERSION,
57
- platform: state.platform,
58
- client: state.client,
59
- agentId: state.agentId,
60
- weeklyRemainingPct: state.weeklyRemainingPct,
61
- fiveHourRemainingPct: state.fiveHourRemainingPct,
62
- weeklyResetAt: state.weeklyResetAt,
63
- fiveHourResetAt: state.fiveHourResetAt,
64
- updatedAt: state.updatedAt
65
- }, null, 2), "utf8");
66
- }
67
-
68
- function normalizeUsageState(raw = {}) {
69
- const platform = normalizePlatform(raw.platform || DEFAULT_PLATFORM);
70
- const normalizedClient = String(raw.client || "").trim();
71
- return {
72
- platform,
73
- client: normalizedClient || resolveClientForPlatform(platform),
74
- agentId: String(raw.agentId || ""),
75
- weeklyRemainingPct: clampPercent(raw.weeklyRemainingPct),
76
- fiveHourRemainingPct: clampPercent(raw.fiveHourRemainingPct),
77
- weeklyResetAt: String(raw.weeklyResetAt || ""),
78
- fiveHourResetAt: String(raw.fiveHourResetAt || ""),
79
- updatedAt: String(raw.updatedAt || "")
80
- };
81
- }
82
-
83
- function clampPercent(value) {
84
- if (value === null || value === undefined || Number.isNaN(Number(value))) return null;
85
- return Math.min(100, Math.max(0, Number(value)));
86
- }
87
-
88
- function resolveTaskGrade(input = "") {
89
- const grade = String(input || "").trim().toUpperCase();
90
- if (!VALID_TASK_GRADES.has(grade)) return "";
91
- return grade;
92
- }
93
-
94
- function normalizePlatform(input = "") {
95
- const key = String(input || "").trim().toLowerCase();
96
- if (!key) return DEFAULT_PLATFORM;
97
- return PLATFORM_ALIASES[key] || key;
98
- }
99
-
100
- function resolveClientForPlatform(platform) {
101
- return PLATFORM_DEFAULT_CLIENT[platform] || "Codex";
102
- }
103
-
104
- function buildBudgetAdvice(state, taskGrade) {
105
- const weekly = state.weeklyRemainingPct;
106
- const fiveHour = state.fiveHourRemainingPct;
107
- const tight = weekly !== null && weekly <= 20 || fiveHour !== null && fiveHour <= 15;
108
- const critical = weekly !== null && weekly <= 10 || fiveHour !== null && fiveHour <= 8;
109
-
110
- if (!taskGrade) {
111
- return {
112
- budget: critical ? "phase1-only" : tight ? "split-large-tasks" : "normal",
113
- gate: critical ? "no broad work" : tight ? "keep scope narrow" : "ok",
114
- next: critical ? "ticket status" : "usage advise --task-grade A"
115
- };
116
- }
117
-
118
- if (critical) {
119
- return {
120
- budget: `${taskGrade} blocked`,
121
- gate: "phase1-only",
122
- next: "summarize and stop"
123
- };
124
- }
125
-
126
- if (tight) {
127
- if (taskGrade === "S" || taskGrade === "A") {
128
- return {
129
- budget: `${taskGrade} split`,
130
- gate: "no broad refactor",
131
- next: "split ticket"
132
- };
133
- }
134
- return {
135
- budget: `${taskGrade} ok`,
136
- gate: "keep local",
137
- next: "avoid broad search"
138
- };
139
- }
140
-
141
- if (taskGrade === "S") {
142
- return {
143
- budget: "S ok with ticket",
144
- gate: "verify before execute",
145
- next: "keep one active task"
146
- };
147
- }
148
- if (taskGrade === "A") {
149
- return {
150
- budget: "A ok",
151
- gate: "normal",
152
- next: "execute"
153
- };
154
- }
155
- if (taskGrade === "B") {
156
- return {
157
- budget: "B ok",
158
- gate: "normal",
159
- next: "keep concise"
160
- };
161
- }
162
- return {
163
- budget: "C ok",
164
- gate: "normal",
165
- next: "stay compact"
166
- };
167
- }
168
-
169
- function buildConversationAdvice(state, opts = {}, budgetAdvice = {}) {
170
- const turnCount = Number(opts.turnCount || 0);
171
- const linkedTicketCount = Number(opts.linkedTicketCount || 0);
172
- const crossWorkspace = Boolean(opts.crossWorkspace);
173
- const weekly = state.weeklyRemainingPct;
174
- const fiveHour = state.fiveHourRemainingPct;
175
- const lowFiveHour = fiveHour !== null && fiveHour <= 15;
176
- const criticalFiveHour = fiveHour !== null && fiveHour <= 8;
177
- const lowWeekly = weekly !== null && weekly <= 20;
178
- const highTurns = turnCount >= 25;
179
- const veryHighTurns = turnCount >= 40;
180
- const manyLinkedTickets = linkedTicketCount >= 3;
181
-
182
- if (criticalFiveHour || (lowFiveHour && veryHighTurns) || (lowFiveHour && crossWorkspace && highTurns)) {
183
- return {
184
- conversationAction: "handoff-and-new-chat",
185
- conversationGate: "handoff now",
186
- conversationNext: "handoff and start new chat"
187
- };
188
- }
189
-
190
- if ((crossWorkspace && highTurns) || manyLinkedTickets || (lowWeekly && highTurns)) {
191
- return {
192
- conversationAction: "split-chat",
193
- conversationGate: "split context",
194
- conversationNext: "split ticket or new chat"
195
- };
196
- }
197
-
198
- if (budgetAdvice.gate === "phase1-only") {
199
- return {
200
- conversationAction: "stop-in-current-chat",
201
- conversationGate: "phase1-only",
202
- conversationNext: "summarize and stop"
203
- };
204
- }
205
-
206
- return {
207
- conversationAction: "keep-current-chat",
208
- conversationGate: "keep current chat",
209
- conversationNext: "continue"
210
- };
211
- }
212
-
213
- async function runUsageSet(opts) {
214
- const current = loadUsageState(opts.cwd) || normalizeUsageState();
215
- const hasPlatformOverride = Boolean(String(opts.platform || "").trim());
216
- const nextPlatform = normalizePlatform(opts.platform || current.platform || DEFAULT_PLATFORM);
217
- const incomingClient = String(opts.client || "").trim();
218
- const resolvedClient = incomingClient
219
- || (hasPlatformOverride
220
- ? resolveClientForPlatform(nextPlatform)
221
- : (current.client || resolveClientForPlatform(nextPlatform)));
222
- const next = normalizeUsageState({
223
- ...current,
224
- platform: nextPlatform,
225
- client: resolvedClient,
226
- agentId: opts.agentId || current.agentId,
227
- weeklyRemainingPct: opts.weeklyRemaining !== null ? opts.weeklyRemaining : current.weeklyRemainingPct,
228
- fiveHourRemainingPct: opts.fiveHourRemaining !== null ? opts.fiveHourRemaining : current.fiveHourRemainingPct,
229
- weeklyResetAt: opts.weeklyReset || current.weeklyResetAt,
230
- fiveHourResetAt: opts.fiveHourReset || current.fiveHourResetAt,
231
- updatedAt: new Date().toISOString()
232
- });
233
-
234
- writeUsageState(opts.cwd, next);
235
-
236
- if (opts.json) {
237
- console.log(JSON.stringify(next, null, 2));
238
- return;
239
- }
240
-
241
- console.log(`usage: ${next.client} weekly ${formatPct(next.weeklyRemainingPct)}, 5h ${formatPct(next.fiveHourRemainingPct)}`);
242
- if (next.agentId) console.log(`agent: ${next.agentId}`);
243
- if (next.weeklyResetAt) console.log(`reset: weekly ${next.weeklyResetAt}`);
244
- if (next.fiveHourResetAt) console.log(`reset: 5h ${next.fiveHourResetAt}`);
245
- }
246
-
247
- async function runUsageStatus(opts) {
248
- const state = loadUsageState(opts.cwd);
249
- if (!state) {
250
- const message = "usage not set";
251
- if (opts.json) {
252
- console.log(JSON.stringify({ status: message }, null, 2));
253
- return;
254
- }
255
- console.log(message);
256
- return;
257
- }
258
-
259
- if (opts.json) {
260
- console.log(JSON.stringify(state, null, 2));
261
- return;
262
- }
263
-
264
- console.log(`usage: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)}`);
265
- if (state.agentId) console.log(`agent: ${state.agentId}`);
266
- if (state.weeklyResetAt) console.log(`reset: weekly ${state.weeklyResetAt}`);
267
- if (state.fiveHourResetAt) console.log(`reset: 5h ${state.fiveHourResetAt}`);
268
- }
269
-
270
- async function runUsageAdvise(opts) {
271
- const state = loadUsageState(opts.cwd);
272
- if (!state) {
273
- const message = "usage not set";
274
- if (opts.json) {
275
- console.log(JSON.stringify({ status: message }, null, 2));
276
- return;
277
- }
278
- console.log(message);
279
- return;
280
- }
281
-
282
- const taskGrade = resolveTaskGrade(opts.taskGrade);
283
- const advice = buildBudgetAdvice(state, taskGrade);
284
- const conversation = buildConversationAdvice(state, opts, advice);
285
- const payload = {
286
- ...state,
287
- taskGrade: taskGrade || null,
288
- taskLabel: opts.taskLabel || "",
289
- turnCount: Number(opts.turnCount || 0),
290
- linkedTicketCount: Number(opts.linkedTicketCount || 0),
291
- crossWorkspace: Boolean(opts.crossWorkspace),
292
- ...advice,
293
- ...conversation
294
- };
295
-
296
- if (opts.json) {
297
- console.log(JSON.stringify(payload, null, 2));
298
- return;
299
- }
300
-
301
- console.log(`usage: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)}`);
302
- if (state.agentId) console.log(`agent: ${state.agentId}`);
303
- console.log(`budget: ${advice.budget}`);
304
- console.log(`${advice.gate === "ok" ? "next" : "gate"}: ${advice.gate === "ok" ? advice.next : advice.gate}`);
305
- if (advice.gate !== "ok") console.log(`next: ${advice.next}`);
306
- if (conversation.conversationAction !== "keep-current-chat") {
307
- console.log(`chat: ${conversation.conversationNext}`);
308
- }
309
- }
310
-
311
- function formatPct(value) {
312
- return value === null ? "n/a" : `${value}%`;
313
- }
314
-
315
- export function getUsageReminderLine(cwd, opts = {}) {
316
- const state = loadUsageState(cwd);
317
- if (!state) return "";
318
- const taskGrade = resolveTaskGrade(opts.taskGrade || "");
319
- const advice = buildBudgetAdvice(state, taskGrade);
320
- const conversation = buildConversationAdvice(state, opts, advice);
321
- const gateText = advice.gate === "ok" ? advice.next : advice.gate;
322
- const chatText = conversation.conversationAction === "keep-current-chat"
323
- ? "keep current chat"
324
- : conversation.conversationNext;
325
- return `usage reminder: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)} | budget ${advice.budget} | gate ${gateText} | chat ${chatText}`;
326
- }
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { DEUK_ROOT_DIR, CliOpts, makePath } from "./cli-utils.js";
3
+
4
+ const USAGE_FILENAME = `${DEUK_ROOT_DIR}/usage.json`;
5
+ const USAGE_VERSION = 1;
6
+ const VALID_TASK_GRADES = new Set(["S", "A", "B", "C"]);
7
+ const DEFAULT_PLATFORM = "codex";
8
+ const PLATFORM_ALIASES = {
9
+ codex: "codex",
10
+ copilot: "copilot",
11
+ ghcopilot: "copilot",
12
+ "github-copilot": "copilot",
13
+ claude: "claude",
14
+ claudecode: "claude",
15
+ cursor: "cursor"
16
+ };
17
+ const PLATFORM_DEFAULT_CLIENT = {
18
+ codex: "Codex",
19
+ copilot: "Copilot",
20
+ claude: "ClaudeCode",
21
+ cursor: "Cursor"
22
+ };
23
+
24
+ export async function runUsage(action, opts) {
25
+ if (action === "set") {
26
+ return runUsageSet(opts);
27
+ }
28
+ if (action === "status") {
29
+ return runUsageStatus(opts);
30
+ }
31
+ if (action === "advise") {
32
+ return runUsageAdvise(opts);
33
+ }
34
+ console.error("Unknown usage action: " + action);
35
+ console.log("Usage: npx deuk-agent-flow usage <set|status|advise> [options]");
36
+ }
37
+
38
+ export function loadUsageState(cwd) {
39
+ const absPath = makePath(cwd, USAGE_FILENAME);
40
+ if (!existsSync(absPath)) return null;
41
+ try {
42
+ const data = JSON.parse(readFileSync(absPath, "utf8"));
43
+ if (data.version !== USAGE_VERSION) return null;
44
+ return normalizeUsageState(data);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ function writeUsageState(cwd, state) {
51
+ const absPath = makePath(cwd, USAGE_FILENAME);
52
+ const dir = makePath(cwd, DEUK_ROOT_DIR);
53
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
54
+ writeFileSync(absPath, JSON.stringify({
55
+ version: USAGE_VERSION,
56
+ platform: state.platform,
57
+ client: state.client,
58
+ agentId: state.agentId,
59
+ weeklyRemainingPct: state.weeklyRemainingPct,
60
+ fiveHourRemainingPct: state.fiveHourRemainingPct,
61
+ weeklyResetAt: state.weeklyResetAt,
62
+ fiveHourResetAt: state.fiveHourResetAt,
63
+ updatedAt: state.updatedAt
64
+ }, null, 2), "utf8");
65
+ }
66
+
67
+ function normalizeUsageState(raw: Record<string, any> = {}) {
68
+ const platform = normalizePlatform(raw.platform || DEFAULT_PLATFORM);
69
+ const normalizedClient = String(raw.client || "").trim();
70
+ return {
71
+ platform,
72
+ client: normalizedClient || resolveClientForPlatform(platform),
73
+ agentId: String(raw.agentId || ""),
74
+ weeklyRemainingPct: clampPercent(raw.weeklyRemainingPct),
75
+ fiveHourRemainingPct: clampPercent(raw.fiveHourRemainingPct),
76
+ weeklyResetAt: String(raw.weeklyResetAt || ""),
77
+ fiveHourResetAt: String(raw.fiveHourResetAt || ""),
78
+ updatedAt: String(raw.updatedAt || "")
79
+ };
80
+ }
81
+
82
+ function clampPercent(value) {
83
+ if (value === null || value === undefined || Number.isNaN(Number(value))) return null;
84
+ return Math.min(100, Math.max(0, Number(value)));
85
+ }
86
+
87
+ function resolveTaskGrade(input = "") {
88
+ const grade = String(input || "").trim().toUpperCase();
89
+ if (!VALID_TASK_GRADES.has(grade)) return "";
90
+ return grade;
91
+ }
92
+
93
+ function normalizePlatform(input = "") {
94
+ const key = String(input || "").trim().toLowerCase();
95
+ if (!key) return DEFAULT_PLATFORM;
96
+ return PLATFORM_ALIASES[key] || key;
97
+ }
98
+
99
+ function resolveClientForPlatform(platform) {
100
+ return PLATFORM_DEFAULT_CLIENT[platform] || "Codex";
101
+ }
102
+
103
+ function buildBudgetAdvice(state, taskGrade) {
104
+ const weekly = state.weeklyRemainingPct;
105
+ const fiveHour = state.fiveHourRemainingPct;
106
+ const tight = weekly !== null && weekly <= 20 || fiveHour !== null && fiveHour <= 15;
107
+ const critical = weekly !== null && weekly <= 10 || fiveHour !== null && fiveHour <= 8;
108
+
109
+ if (!taskGrade) {
110
+ return {
111
+ budget: critical ? "phase1-only" : tight ? "split-large-tasks" : "normal",
112
+ gate: critical ? "no broad work" : tight ? "keep scope narrow" : "ok",
113
+ next: critical ? "ticket status" : "usage advise --task-grade A"
114
+ };
115
+ }
116
+
117
+ if (critical) {
118
+ return {
119
+ budget: `${taskGrade} blocked`,
120
+ gate: "phase1-only",
121
+ next: "summarize and stop"
122
+ };
123
+ }
124
+
125
+ if (tight) {
126
+ if (taskGrade === "S" || taskGrade === "A") {
127
+ return {
128
+ budget: `${taskGrade} split`,
129
+ gate: "no broad refactor",
130
+ next: "split ticket"
131
+ };
132
+ }
133
+ return {
134
+ budget: `${taskGrade} ok`,
135
+ gate: "keep local",
136
+ next: "avoid broad search"
137
+ };
138
+ }
139
+
140
+ if (taskGrade === "S") {
141
+ return {
142
+ budget: "S ok with ticket",
143
+ gate: "verify before execute",
144
+ next: "keep one active task"
145
+ };
146
+ }
147
+ if (taskGrade === "A") {
148
+ return {
149
+ budget: "A ok",
150
+ gate: "normal",
151
+ next: "execute"
152
+ };
153
+ }
154
+ if (taskGrade === "B") {
155
+ return {
156
+ budget: "B ok",
157
+ gate: "normal",
158
+ next: "keep concise"
159
+ };
160
+ }
161
+ return {
162
+ budget: "C ok",
163
+ gate: "normal",
164
+ next: "stay compact"
165
+ };
166
+ }
167
+
168
+ function buildConversationAdvice(state, opts: CliOpts = {}, budgetAdvice: Record<string, any> = {}) {
169
+ const turnCount = Number(opts.turnCount || 0);
170
+ const linkedTicketCount = Number(opts.linkedTicketCount || 0);
171
+ const crossWorkspace = Boolean(opts.crossWorkspace);
172
+ const weekly = state.weeklyRemainingPct;
173
+ const fiveHour = state.fiveHourRemainingPct;
174
+ const lowFiveHour = fiveHour !== null && fiveHour <= 15;
175
+ const criticalFiveHour = fiveHour !== null && fiveHour <= 8;
176
+ const lowWeekly = weekly !== null && weekly <= 20;
177
+ const highTurns = turnCount >= 25;
178
+ const veryHighTurns = turnCount >= 40;
179
+ const manyLinkedTickets = linkedTicketCount >= 3;
180
+
181
+ if (criticalFiveHour || (lowFiveHour && veryHighTurns) || (lowFiveHour && crossWorkspace && highTurns)) {
182
+ return {
183
+ conversationAction: "handoff-and-new-chat",
184
+ conversationGate: "handoff now",
185
+ conversationNext: "handoff and start new chat"
186
+ };
187
+ }
188
+
189
+ if ((crossWorkspace && highTurns) || manyLinkedTickets || (lowWeekly && highTurns)) {
190
+ return {
191
+ conversationAction: "split-chat",
192
+ conversationGate: "split context",
193
+ conversationNext: "split ticket or new chat"
194
+ };
195
+ }
196
+
197
+ if (budgetAdvice.gate === "phase1-only") {
198
+ return {
199
+ conversationAction: "stop-in-current-chat",
200
+ conversationGate: "phase1-only",
201
+ conversationNext: "summarize and stop"
202
+ };
203
+ }
204
+
205
+ return {
206
+ conversationAction: "keep-current-chat",
207
+ conversationGate: "keep current chat",
208
+ conversationNext: "continue"
209
+ };
210
+ }
211
+
212
+ async function runUsageSet(opts) {
213
+ const current = loadUsageState(opts.cwd) || normalizeUsageState();
214
+ const hasPlatformOverride = Boolean(String(opts.platform || "").trim());
215
+ const nextPlatform = normalizePlatform(opts.platform || current.platform || DEFAULT_PLATFORM);
216
+ const incomingClient = String(opts.client || "").trim();
217
+ const resolvedClient = incomingClient
218
+ || (hasPlatformOverride
219
+ ? resolveClientForPlatform(nextPlatform)
220
+ : (current.client || resolveClientForPlatform(nextPlatform)));
221
+ const next = normalizeUsageState({
222
+ ...current,
223
+ platform: nextPlatform,
224
+ client: resolvedClient,
225
+ agentId: opts.agentId || current.agentId,
226
+ weeklyRemainingPct: opts.weeklyRemaining !== null ? opts.weeklyRemaining : current.weeklyRemainingPct,
227
+ fiveHourRemainingPct: opts.fiveHourRemaining !== null ? opts.fiveHourRemaining : current.fiveHourRemainingPct,
228
+ weeklyResetAt: opts.weeklyReset || current.weeklyResetAt,
229
+ fiveHourResetAt: opts.fiveHourReset || current.fiveHourResetAt,
230
+ updatedAt: new Date().toISOString()
231
+ });
232
+
233
+ writeUsageState(opts.cwd, next);
234
+
235
+ if (opts.json) {
236
+ console.log(JSON.stringify(next, null, 2));
237
+ return;
238
+ }
239
+
240
+ console.log(`usage: ${next.client} weekly ${formatPct(next.weeklyRemainingPct)}, 5h ${formatPct(next.fiveHourRemainingPct)}`);
241
+ if (next.agentId) console.log(`agent: ${next.agentId}`);
242
+ if (next.weeklyResetAt) console.log(`reset: weekly ${next.weeklyResetAt}`);
243
+ if (next.fiveHourResetAt) console.log(`reset: 5h ${next.fiveHourResetAt}`);
244
+ }
245
+
246
+ async function runUsageStatus(opts) {
247
+ const state = loadUsageState(opts.cwd);
248
+ if (!state) {
249
+ const message = "usage not set";
250
+ if (opts.json) {
251
+ console.log(JSON.stringify({ status: message }, null, 2));
252
+ return;
253
+ }
254
+ console.log(message);
255
+ return;
256
+ }
257
+
258
+ if (opts.json) {
259
+ console.log(JSON.stringify(state, null, 2));
260
+ return;
261
+ }
262
+
263
+ console.log(`usage: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)}`);
264
+ if (state.agentId) console.log(`agent: ${state.agentId}`);
265
+ if (state.weeklyResetAt) console.log(`reset: weekly ${state.weeklyResetAt}`);
266
+ if (state.fiveHourResetAt) console.log(`reset: 5h ${state.fiveHourResetAt}`);
267
+ }
268
+
269
+ async function runUsageAdvise(opts) {
270
+ const state = loadUsageState(opts.cwd);
271
+ if (!state) {
272
+ const message = "usage not set";
273
+ if (opts.json) {
274
+ console.log(JSON.stringify({ status: message }, null, 2));
275
+ return;
276
+ }
277
+ console.log(message);
278
+ return;
279
+ }
280
+
281
+ const taskGrade = resolveTaskGrade(opts.taskGrade);
282
+ const advice = buildBudgetAdvice(state, taskGrade);
283
+ const conversation = buildConversationAdvice(state, opts, advice);
284
+ const payload = {
285
+ ...state,
286
+ taskGrade: taskGrade || null,
287
+ taskLabel: opts.taskLabel || "",
288
+ turnCount: Number(opts.turnCount || 0),
289
+ linkedTicketCount: Number(opts.linkedTicketCount || 0),
290
+ crossWorkspace: Boolean(opts.crossWorkspace),
291
+ ...advice,
292
+ ...conversation
293
+ };
294
+
295
+ if (opts.json) {
296
+ console.log(JSON.stringify(payload, null, 2));
297
+ return;
298
+ }
299
+
300
+ console.log(`usage: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)}`);
301
+ if (state.agentId) console.log(`agent: ${state.agentId}`);
302
+ console.log(`budget: ${advice.budget}`);
303
+ console.log(`${advice.gate === "ok" ? "next" : "gate"}: ${advice.gate === "ok" ? advice.next : advice.gate}`);
304
+ if (advice.gate !== "ok") console.log(`next: ${advice.next}`);
305
+ if (conversation.conversationAction !== "keep-current-chat") {
306
+ console.log(`chat: ${conversation.conversationNext}`);
307
+ }
308
+ }
309
+
310
+ function formatPct(value) {
311
+ return value === null ? "n/a" : `${value}%`;
312
+ }
313
+
314
+ export function getUsageReminderLine(cwd, opts: CliOpts = {}) {
315
+ const state = loadUsageState(cwd);
316
+ if (!state) return "";
317
+ const taskGrade = resolveTaskGrade(opts.taskGrade || "");
318
+ const advice = buildBudgetAdvice(state, taskGrade);
319
+ const conversation = buildConversationAdvice(state, opts, advice);
320
+ const gateText = advice.gate === "ok" ? advice.next : advice.gate;
321
+ const chatText = conversation.conversationAction === "keep-current-chat"
322
+ ? "keep current chat"
323
+ : conversation.conversationNext;
324
+ return `usage reminder: ${state.client} weekly ${formatPct(state.weeklyRemainingPct)}, 5h ${formatPct(state.fiveHourRemainingPct)} | budget ${advice.budget} | gate ${gateText} | chat ${chatText}`;
325
+ }