howtofixcli 0.7.14 → 0.7.16

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 (165) hide show
  1. package/dist/chunk-27TRKD65.js +2 -0
  2. package/dist/chunk-2XG6XQZR.js +1 -0
  3. package/dist/chunk-3FYCFLNK.js +1 -0
  4. package/dist/chunk-56SEAGAQ.js +1 -0
  5. package/dist/chunk-5HR4WOW7.js +2 -0
  6. package/dist/chunk-6FUWMLWO.js +1 -0
  7. package/dist/chunk-7CHDNTH6.js +1 -0
  8. package/dist/chunk-A5WOL324.js +34 -0
  9. package/dist/chunk-AGGIYXN7.js +2 -0
  10. package/dist/chunk-AUDMRGWF.js +189 -0
  11. package/dist/chunk-BX5KIC4R.js +2 -0
  12. package/dist/chunk-DAPXGSGD.js +12 -0
  13. package/dist/chunk-DOD7MUGX.js +1 -0
  14. package/dist/chunk-DVYH677E.js +5 -0
  15. package/dist/chunk-FFNDHUCQ.js +5 -0
  16. package/dist/chunk-FPXVDP32.js +8 -0
  17. package/dist/chunk-GXB7RZHH.js +1 -0
  18. package/dist/chunk-ILDDDLRC.js +1 -0
  19. package/dist/chunk-JFYVRWJI.js +2 -0
  20. package/dist/chunk-LAHVHRCS.js +2 -0
  21. package/dist/chunk-LJEKOS4S.js +6 -0
  22. package/dist/chunk-LSJC73DG.js +1 -0
  23. package/dist/chunk-MOKUBKE7.js +1 -0
  24. package/dist/chunk-MVTU4M3W.js +2 -0
  25. package/dist/chunk-N345KWWK.js +1 -0
  26. package/dist/chunk-ONZLNFQ2.js +3 -0
  27. package/dist/chunk-ORJ2QZBG.js +3 -0
  28. package/dist/chunk-PCALBZOO.js +2 -0
  29. package/dist/chunk-Q7VRW74X.js +1 -0
  30. package/dist/chunk-QJ6MIM5Z.js +1 -0
  31. package/dist/chunk-TSEKPGK4.js +1 -0
  32. package/dist/chunk-UTYFLLSM.js +1 -0
  33. package/dist/chunk-V3R4FYTJ.js +1 -0
  34. package/dist/chunk-V656XVXV.js +8 -0
  35. package/dist/chunk-VLMQSD25.js +1 -0
  36. package/dist/chunk-WKUJ7CFO.js +1 -0
  37. package/dist/chunk-WLU3WFCK.js +1 -0
  38. package/dist/chunk-WOT6VMZA.js +1 -0
  39. package/dist/chunk-WUPU6LTD.js +4 -0
  40. package/dist/chunk-WX3V5P7U.js +5 -0
  41. package/dist/chunk-YW7NORH3.js +2 -0
  42. package/dist/chunk-ZNS6H44M.js +110 -0
  43. package/dist/main.js +99 -551
  44. package/package.json +17 -64
  45. package/dist/account-credential.js +0 -136
  46. package/dist/account-credential.test.js +0 -38
  47. package/dist/acp.js +0 -234
  48. package/dist/activity.js +0 -124
  49. package/dist/agent-defs.js +0 -237
  50. package/dist/agent-limiter.js +0 -68
  51. package/dist/agents.js +0 -554
  52. package/dist/anim.js +0 -361
  53. package/dist/args.js +0 -250
  54. package/dist/at-file.js +0 -28
  55. package/dist/brand-extension.js +0 -87
  56. package/dist/brand.js +0 -76
  57. package/dist/browser.js +0 -58
  58. package/dist/cache-report.js +0 -139
  59. package/dist/checkpoint.js +0 -437
  60. package/dist/claims.js +0 -167
  61. package/dist/cli-auth.js +0 -142
  62. package/dist/cli-checkin.js +0 -120
  63. package/dist/cli-credentials.js +0 -254
  64. package/dist/clipboard.js +0 -149
  65. package/dist/collapse.js +0 -129
  66. package/dist/commands.js +0 -294
  67. package/dist/compaction-guard.js +0 -81
  68. package/dist/context-extension.js +0 -17
  69. package/dist/dcp/commands.js +0 -172
  70. package/dist/dcp/compress-state.js +0 -139
  71. package/dist/dcp/compress.js +0 -329
  72. package/dist/dcp/dcp-extension.js +0 -295
  73. package/dist/dcp/dedup.js +0 -93
  74. package/dist/dcp/ids.js +0 -189
  75. package/dist/dcp/index.js +0 -25
  76. package/dist/dcp/middle-prune.js +0 -76
  77. package/dist/dcp/prompts.js +0 -68
  78. package/dist/dcp/protected-content.js +0 -221
  79. package/dist/dcp/protected.js +0 -161
  80. package/dist/dcp/prune.js +0 -418
  81. package/dist/dcp/state.js +0 -176
  82. package/dist/device-id.js +0 -90
  83. package/dist/device-id.test.js +0 -23
  84. package/dist/errors.js +0 -63
  85. package/dist/extensions.js +0 -48
  86. package/dist/fatal.js +0 -35
  87. package/dist/feed.js +0 -498
  88. package/dist/fs-atomic.js +0 -54
  89. package/dist/gateway-errors.js +0 -90
  90. package/dist/git.js +0 -230
  91. package/dist/goal-extension.js +0 -88
  92. package/dist/goal.js +0 -222
  93. package/dist/hooks-extension.js +0 -176
  94. package/dist/hooks.js +0 -443
  95. package/dist/howtofix-account.js +0 -735
  96. package/dist/hud-line.js +0 -153
  97. package/dist/hwid.js +0 -130
  98. package/dist/interrupt.js +0 -60
  99. package/dist/json-events.js +0 -50
  100. package/dist/level-style.js +0 -112
  101. package/dist/loop-guard.js +0 -95
  102. package/dist/lsp-tools.js +0 -32
  103. package/dist/lsp.js +0 -429
  104. package/dist/mcp-config.js +0 -85
  105. package/dist/mcp-lifecycle.js +0 -147
  106. package/dist/model-catalog.js +0 -217
  107. package/dist/model-facts.js +0 -35
  108. package/dist/model-resolve.js +0 -65
  109. package/dist/notes.js +0 -78
  110. package/dist/oauth-login.js +0 -568
  111. package/dist/paths.js +0 -61
  112. package/dist/permissions.js +0 -298
  113. package/dist/plan-mode.js +0 -363
  114. package/dist/preset-command.js +0 -227
  115. package/dist/preset-extension.js +0 -35
  116. package/dist/preset-mcp.js +0 -292
  117. package/dist/presets.js +0 -727
  118. package/dist/prompt-files.js +0 -103
  119. package/dist/prompt-text.js +0 -41
  120. package/dist/proxy.js +0 -137
  121. package/dist/quality/ast.js +0 -196
  122. package/dist/quality/cognitive.js +0 -83
  123. package/dist/quality/cyclomatic.js +0 -30
  124. package/dist/quality/halstead.js +0 -83
  125. package/dist/quality/index.js +0 -18
  126. package/dist/quality/length.js +0 -3
  127. package/dist/quality/maintainability.js +0 -15
  128. package/dist/quality/nesting.js +0 -57
  129. package/dist/quality/report.js +0 -803
  130. package/dist/quality/smells.js +0 -67
  131. package/dist/quality-tool.js +0 -63
  132. package/dist/read-only-commands.js +0 -597
  133. package/dist/repo-map.js +0 -292
  134. package/dist/roblox-connect.js +0 -125
  135. package/dist/rpc.js +0 -91
  136. package/dist/rtk.js +0 -193
  137. package/dist/sandbox.js +0 -105
  138. package/dist/schema.js +0 -214
  139. package/dist/serve.js +0 -50
  140. package/dist/session-lifecycle.js +0 -19
  141. package/dist/session-retention.js +0 -111
  142. package/dist/session.js +0 -447
  143. package/dist/sessions.js +0 -100
  144. package/dist/settings.js +0 -148
  145. package/dist/skills.js +0 -35
  146. package/dist/slop-tool.js +0 -96
  147. package/dist/spend-cap.js +0 -43
  148. package/dist/subagent.js +0 -171
  149. package/dist/tar.js +0 -146
  150. package/dist/theme.js +0 -79
  151. package/dist/thinking.js +0 -45
  152. package/dist/todo.js +0 -114
  153. package/dist/tool-loadout.js +0 -97
  154. package/dist/tool-render.js +0 -769
  155. package/dist/tools.js +0 -176
  156. package/dist/ui.js +0 -4814
  157. package/dist/update.js +0 -551
  158. package/dist/usage-segment.js +0 -69
  159. package/dist/web-tools.js +0 -46
  160. package/dist/web.js +0 -265
  161. package/dist/welcome.js +0 -179
  162. package/dist/workflow-engine.js +0 -246
  163. package/dist/workflow-progress.js +0 -446
  164. package/dist/workflow-view.js +0 -195
  165. package/dist/workflow.js +0 -349
@@ -1,237 +0,0 @@
1
- import { readFileSync, readdirSync, statSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { getAgentDir, parseFrontmatter } from "@netrodev/hxf-coding-agent";
4
- import { BUILTIN_AGENT_NAMES } from "./agents.js";
5
- /**
6
- * Subagent definitions written by the user, as markdown.
7
- *
8
- * One file is one agent. The frontmatter carries what the model needs to choose
9
- * it and what the run needs to be set up, and the body becomes the child's
10
- * standing system prompt:
11
- *
12
- * ---
13
- * name: migration
14
- * description: moves one module at a time and tests after each
15
- * tools: read, bash, edit, write
16
- * model: gemini-3-flash
17
- * effort: medium
18
- * ---
19
- * Migrate exactly one module per turn. Run its tests before moving on, and
20
- * report the file you changed and the test result.
21
- *
22
- * The parser is the harness's own, so a file means the same thing here and in
23
- * the harness. The locations follow this CLI's rule: everything a user writes
24
- * for a project lives under one directory, `.howtofix`, the same one that holds
25
- * project skills and presets.
26
- *
27
- * <project>/.howtofix/agents/*.md nearest ancestor, found by walking up
28
- * ~/.howtofix/agent/agents/*.md the CLI's own user scope
29
- *
30
- * `parseFrontmatter` is the SDK's YAML parser, which means quoted values,
31
- * arrays, comments and anything else YAML allows are read the same way the
32
- * harness reads them. The user-scope directory is the one the run store also
33
- * uses (`AGENTS_STORE_DIR`); the two do not collide because the store reads only
34
- * `*.json` and this reads only `*.md`.
35
- *
36
- * A definition replaces a built-in of the same name. That is the point of having
37
- * them: `scout`, `architect`, `task` and `reviewer` are a starting set, not a
38
- * fixed menu, and an agent whose prompt is wrong for a project should be fixable
39
- * without patching this package.
40
- */
41
- /** The effort levels a definition may declare. Ours, not the harness's. */
42
- const EFFORTS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
43
- /** Frontmatter keys this loader reads; anything else is reported and ignored. */
44
- const KEYS = ["name", "description", "tools", "model", "effort"];
45
- /**
46
- * Tools a definition may name.
47
- *
48
- * Not a permission list — the permission extension still decides what a child
49
- * may actually do — but a spelling list, so a typo is reported at load instead of
50
- * silently resolving to nothing at call time. `mcp__<server>__<tool>` names are
51
- * accepted as written because they cannot be known in advance.
52
- */
53
- export const KNOWN_AGENT_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls", "bash_output", "kill_shell", "todo", "context", "goal", "task", "workflow", "web_search", "web_fetch", "lsp", "quality", "claim", "repo_map"];
54
- /**
55
- * What a definition gets when it names no tools: the same set the built-in `task`
56
- * agent has.
57
- *
58
- * The harness leaves an absent `tools` meaning "no restriction". Here the field
59
- * is the child's allowlist, so absent has to mean something concrete, and a
60
- * working set is the useful reading: the permission extension already refuses
61
- * anything above the parent's approval mode inside a child, and a spawned agent
62
- * has no interactive UI, so the gate fails closed. Narrowing is what `tools` is
63
- * for.
64
- */
65
- export const DEFAULT_AGENT_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls", "context", "todo", "web_search", "web_fetch", "lsp", "claim"];
66
- /** `a, b` and `[a, b]` both mean two tools; anything else means none declared. */
67
- function asToolList(value) {
68
- const raw = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value : [];
69
- const tools = raw
70
- .map((item) => String(item)
71
- .trim()
72
- .replace(/^["']|["']$/g, ""))
73
- .filter(Boolean);
74
- return tools.length > 0 ? tools : undefined;
75
- }
76
- function isDirectory(path) {
77
- try {
78
- return statSync(path).isDirectory();
79
- }
80
- catch {
81
- return false;
82
- }
83
- }
84
- /**
85
- * The nearest project agent directory, walking up from `cwd`.
86
- *
87
- * Walking up is the harness's rule and the useful one: a definition written for a
88
- * monorepo root applies to every package under it.
89
- */
90
- export function findProjectAgentDir(cwd) {
91
- let current = cwd;
92
- for (;;) {
93
- // Beside project skills and presets: one project directory, .howtofix, so a
94
- // repository does not end up with two dot-directories holding the same kind
95
- // of file.
96
- const candidate = join(current, ".howtofix", "agents");
97
- if (isDirectory(candidate))
98
- return candidate;
99
- const parent = dirname(current);
100
- if (parent === current)
101
- return undefined;
102
- current = parent;
103
- }
104
- }
105
- /**
106
- * The directories definitions are read from, in load order.
107
- *
108
- * User first, project last, and a later definition replaces an earlier one — so a
109
- * project agent wins a name collision, the precedence the harness's own subagent
110
- * discovery uses.
111
- */
112
- export function subagentDirs(cwd) {
113
- // One user location, the one this CLI owns. The harness default, ~/.hxf/agent,
114
- // is not read: two folders holding the same kind of file is how a definition
115
- // ends up in the place nothing looks at.
116
- const dirs = [{ scope: "user", dir: join(getAgentDir(), "agents") }];
117
- const project = findProjectAgentDir(cwd);
118
- if (project)
119
- dirs.push({ scope: "project", dir: project });
120
- return dirs;
121
- }
122
- /**
123
- * Parse one definition file. Pure apart from the SDK's YAML parser, so the rules
124
- * can be tested without touching a filesystem.
125
- */
126
- export function parseAgentDef(text, file) {
127
- const notes = [];
128
- const problems = [];
129
- const source = text.replace(/^\uFEFF/, "");
130
- if (!/^---\r?\n/.test(source)) {
131
- return { notes, problems: [`${file}: no frontmatter — the file must start with --- and end the block with ---`] };
132
- }
133
- let frontmatter = {};
134
- let body = "";
135
- try {
136
- const parsed = parseFrontmatter(source);
137
- frontmatter = parsed.frontmatter ?? {};
138
- body = (parsed.body ?? "").trim();
139
- }
140
- catch (error) {
141
- return { notes, problems: [`${file}: the frontmatter is not readable YAML — ${error instanceof Error ? error.message : String(error)}`] };
142
- }
143
- for (const key of Object.keys(frontmatter)) {
144
- if (!KEYS.includes(key))
145
- notes.push(`${file}: ignoring the unknown frontmatter key '${key}'`);
146
- }
147
- // A file's own name is the natural default, so `reviewer.md` needs no `name:`.
148
- const stem = (file.split(/[\\/]/).pop() ?? file).replace(/\.md$/i, "");
149
- const rawName = frontmatter.name;
150
- const name = (typeof rawName === "string" && rawName.trim() ? rawName : stem).trim();
151
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
152
- problems.push(`${file}: '${name}' is not a usable agent name — letters, digits, dot, dash and underscore only`);
153
- }
154
- const rawDescription = frontmatter.description;
155
- const description = typeof rawDescription === "string" ? rawDescription.trim() : "";
156
- if (!description)
157
- problems.push(`${file}: a description is required — it is how the model decides to use this agent`);
158
- if (!body)
159
- problems.push(`${file}: no prompt body — the text below the frontmatter is the agent's standing instruction`);
160
- const rawEffort = frontmatter.effort;
161
- const effort = typeof rawEffort === "string" ? rawEffort.trim().toLowerCase() : "";
162
- if (effort && !EFFORTS.includes(effort)) {
163
- problems.push(`${file}: effort '${effort}' is not one of ${EFFORTS.join(", ")}`);
164
- }
165
- const declared = asToolList(frontmatter.tools);
166
- const tools = declared ?? [...DEFAULT_AGENT_TOOLS];
167
- if (declared) {
168
- const unknown = declared.filter((tool) => !KNOWN_AGENT_TOOLS.includes(tool) && !tool.startsWith("mcp__"));
169
- if (unknown.length > 0) {
170
- notes.push(`${file}: unknown tool${unknown.length === 1 ? "" : "s"} ${unknown.join(", ")} — kept, but nothing registers them yet`);
171
- }
172
- }
173
- const rawModel = frontmatter.model;
174
- const model = typeof rawModel === "string" && rawModel.trim() ? rawModel.trim() : undefined;
175
- if (problems.length > 0)
176
- return { notes, problems };
177
- return {
178
- def: { name, description, tools, prompt: body, effort: (effort || "medium"), model, source: file },
179
- notes,
180
- problems,
181
- };
182
- }
183
- /**
184
- * Load every definition into `into`.
185
- *
186
- * Re-running cannot shuffle which definition a session runs with: the same files
187
- * produce the same map, and a definition only ever replaces one that loaded
188
- * before it.
189
- */
190
- export function loadAgentDefs(cwd, into) {
191
- const loaded = [];
192
- const notes = [];
193
- const problems = [];
194
- const scopes = [];
195
- for (const { scope, dir } of subagentDirs(cwd)) {
196
- let entries;
197
- try {
198
- entries = readdirSync(dir);
199
- }
200
- catch {
201
- // No directory is the normal case, not a problem to report.
202
- continue;
203
- }
204
- for (const entry of entries.filter((name) => name.toLowerCase().endsWith(".md")).sort()) {
205
- const file = join(dir, entry);
206
- let text;
207
- try {
208
- text = readFileSync(file, "utf8");
209
- }
210
- catch (error) {
211
- problems.push(`${file}: ${error instanceof Error ? error.message : String(error)}`);
212
- continue;
213
- }
214
- const parsed = parseAgentDef(text, file);
215
- notes.push(...parsed.notes);
216
- problems.push(...parsed.problems);
217
- const def = parsed.def;
218
- if (!def)
219
- continue;
220
- if (into.has(def.name)) {
221
- notes.push(`${file}: replaces ${BUILTIN_AGENT_NAMES.has(def.name) ? "the built-in" : "an earlier"} '${def.name}'`);
222
- }
223
- into.set(def.name, def);
224
- loaded.push(def);
225
- if (!scopes.includes(scope))
226
- scopes.push(scope);
227
- }
228
- }
229
- if (loaded.length > 0) {
230
- // The effective set, not every file read: a definition that a later one
231
- // replaced is not what a session will run with, and naming it twice would
232
- // read as two agents.
233
- const effective = loaded.filter((def) => into.get(def.name) === def);
234
- notes.unshift(`loaded ${effective.length} subagent definition${effective.length === 1 ? "" : "s"} (${scopes.join(" and ")}): ${effective.map((def) => def.name).join(", ")}`);
235
- }
236
- return { loaded, notes, problems };
237
- }
@@ -1,68 +0,0 @@
1
- /**
2
- * One budget for agent sessions, shared by every path that opens one.
3
- *
4
- * Two paths spawn children: the `task` tool (`drain`, up to `MAX_PARALLEL` at a
5
- * time) and the `workflow` tool (its own semaphore, `defaultConcurrency()`). Each
6
- * had its own limiter and neither knew about the other, so on a six-core box a
7
- * workflow could run four agents while a task call ran three — seven sessions,
8
- * seven provider streams, on a machine sized for far fewer. Nothing failed; the
9
- * box simply went quiet, which is the worst way for a limit to be missing.
10
- *
11
- * The width is the machine's, not a constant: `os.availableParallelism()`
12
- * respects the cgroup, so a container sized to two cores does not fan out as if
13
- * it owned the host. The `agents` setting overrides it for a user who knows their
14
- * box, and `agents=1` serialises every child — the escape hatch for a host that
15
- * cannot take any concurrency at all.
16
- */
17
- import { availableParallelism } from "node:os";
18
- import { AGENT_DIR } from "./paths.js";
19
- import { readWorkbenchSettings } from "./settings.js";
20
- /** The ceiling, whatever the machine reports. */
21
- export const MAX_AGENT_CONCURRENCY = 4;
22
- /**
23
- * How many agent sessions may be in flight at once, across `task` and `workflow`.
24
- *
25
- * The default leaves the host usable: two cores free for the CLI, the model's
26
- * own request handling, and whatever else the user is running.
27
- */
28
- export function agentConcurrency() {
29
- const configured = readWorkbenchSettings(AGENT_DIR).agents;
30
- if (configured !== undefined)
31
- return configured;
32
- return Math.max(1, Math.min(MAX_AGENT_CONCURRENCY, availableParallelism() - 2));
33
- }
34
- let active = 0;
35
- const waiting = [];
36
- /**
37
- * Take one slot, returning the function that releases it.
38
- *
39
- * FIFO, so a `workflow` that asks for four and a `task` that asks for three are
40
- * served in the order they arrived rather than by whichever retries first — an
41
- * unfair queue starves one path under load, which reads as a hang in that path.
42
- *
43
- * The width is read when the slot is taken, not when the module loads, so
44
- * changing the setting affects the next child rather than the next process.
45
- */
46
- export async function acquireAgentSlot() {
47
- while (active >= agentConcurrency()) {
48
- await new Promise((resolve) => waiting.push(resolve));
49
- }
50
- active += 1;
51
- let released = false;
52
- return () => {
53
- if (released)
54
- return;
55
- released = true;
56
- active -= 1;
57
- waiting.shift()?.();
58
- };
59
- }
60
- /** Sessions in flight right now, for a check and for `/agents`. */
61
- export function activeAgentCount() {
62
- return active;
63
- }
64
- /** Test seam: drop the in-flight count and release everyone waiting. */
65
- export function resetAgentLimiterForTest() {
66
- active = 0;
67
- waiting.splice(0).forEach((resolve) => resolve());
68
- }