@tea-agent/loop-agent 0.21.0 → 0.23.1

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 (108) hide show
  1. package/AGENTS.md +42 -108
  2. package/CHANGELOG.md +114 -0
  3. package/README.md +7 -4
  4. package/bin/agent-worker.js +0 -0
  5. package/dist/adapters/loop-agent.js +52 -0
  6. package/dist/application/context-usage/skill-resolution-stats.js +263 -0
  7. package/dist/application/dag/generate-task-dag.js +17 -3
  8. package/dist/cli/command-definitions.js +8 -7
  9. package/dist/cli/program.js +17 -15
  10. package/dist/commands/doctor.js +269 -18
  11. package/dist/commands/init.js +198 -86
  12. package/dist/commands/stats.js +40 -11
  13. package/dist/executors/dag-pi-executor.js +2 -0
  14. package/dist/executors/shell-executor.js +162 -19
  15. package/dist/shared/openspec-spec.js +49 -0
  16. package/dist/shared/operator/capabilities.js +11 -1
  17. package/dist/worker/console/app-data.js +4 -0
  18. package/dist/worker/console/chat/instruction-skills.js +217 -0
  19. package/dist/worker/console/chat/model-resolver.js +106 -0
  20. package/dist/worker/console/chat/pi-runtime.js +605 -0
  21. package/dist/worker/console/chat/resource-loader.js +66 -0
  22. package/dist/worker/console/chat/routes.js +357 -0
  23. package/dist/worker/console/chat/session-store.js +238 -0
  24. package/dist/worker/console/chat/tool-adapter.js +238 -0
  25. package/dist/worker/console/chat/tools.js +171 -0
  26. package/dist/worker/console/server.js +55 -0
  27. package/dist/worker/console/static/assets/index-DVl7Jxt5.js +25 -0
  28. package/dist/worker/console/static/assets/index-lVcIr9Ju.css +1 -0
  29. package/dist/worker/console/static/index.html +2 -2
  30. package/dist/worker/feature/profile-schema.js +1 -1
  31. package/dist/worker/observability/read-model.js +21 -1
  32. package/dist/worker/observe/spec-evidence.js +12 -15
  33. package/dist/worker/observe/static/dag-helpers.js +22 -0
  34. package/dist/worker/observe/static/views/dag.js +5 -0
  35. package/dist/workflows/dag/backend-test-markdown-workflow.js +37 -0
  36. package/dist/workflows/dag/frontend-implementation-contract.js +141 -32
  37. package/dist/workflows/dag/frontend-lint-baseline.js +471 -0
  38. package/dist/workflows/dag/frontend-prewrite-gate.js +79 -16
  39. package/dist/workflows/dag/frontend-project-capability.js +11 -8
  40. package/dist/workflows/dag/frontend-repair.js +6 -4
  41. package/dist/workflows/dag/frontend-review-context.js +67 -0
  42. package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
  43. package/dist/workflows/dag/frontend-test-result-contract.js +71 -66
  44. package/dist/workflows/dag/frontend-verification-trace.js +31 -1
  45. package/dist/workflows/dag/frontend-worktree-diff.js +81 -6
  46. package/dist/workflows/dag/init-hybrid.js +412 -84
  47. package/dist/workflows/dag/node-execution.js +38 -1
  48. package/dist/workflows/dag/output-protocol.js +89 -0
  49. package/dist/workflows/dag/prompt.js +35 -1
  50. package/dist/workflows/dag/recovery-recommendation.js +45 -0
  51. package/dist/workflows/dag/report.js +28 -1
  52. package/dist/workflows/dag/rerun-task.js +1 -1
  53. package/dist/workflows/dag/scheduler.js +9 -0
  54. package/dist/workflows/dag/types.js +74 -1
  55. package/dist/workflows/dag/validate.js +55 -0
  56. package/docs/README.md +73 -156
  57. package/docs/architecture/README.md +3 -2
  58. package/docs/architecture/dag-execution.md +2 -2
  59. package/docs/architecture/evolution.md +14 -12
  60. package/docs/architecture/system-overview.md +1 -1
  61. package/docs/architecture/worker-and-feature.md +3 -3
  62. package/docs/governance/README.md +15 -0
  63. package/docs/{harness-methodology-debugging.md → governance/harness-methodology-debugging.md} +27 -3
  64. package/docs/init-surface.manifest.json +22 -4
  65. package/docs/operations/README.md +12 -0
  66. package/docs/{local-development-environment.md → operations/local-development-environment.md} +1 -1
  67. package/docs/skills/vetted-skill-registry.md +23 -3
  68. package/docs/templates/README.md +55 -0
  69. package/docs/templates/agent-dag.schema.json +15 -5
  70. package/docs/templates/backend-test-dag.json +1 -1
  71. package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
  72. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
  73. package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
  74. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
  75. package/docs/templates/frontend-implementation-contract.schema.json +4 -3
  76. package/docs/templates/frontend-test-case-checklist.md +6 -2
  77. package/docs/templates/frontend-test-dag.json +2 -2
  78. package/docs/templates/hybrid-dag.json +1 -1
  79. package/docs/templates/progress-log.md +9 -2
  80. package/harness.json +5 -5
  81. package/package.json +5 -5
  82. package/scripts/kb-bootstrap-init-skeleton.sh +2 -2
  83. package/skills/agent-worker/SKILL.md +1 -1
  84. package/skills/frontend-design-review/SKILL.md +12 -10
  85. package/skills/frontend-design-review/references/review-checklist.md +4 -4
  86. package/skills/frontend-implementation/SKILL.md +2 -2
  87. package/skills/frontend-implementation/references/code-standards.md +4 -3
  88. package/skills/frontend-implementation/references/design-spec.md +19 -14
  89. package/skills/frontend-implementation/references/node-contracts.md +2 -2
  90. package/skills/frontend-review/SKILL.md +15 -28
  91. package/skills/frontend-review/references/review-findings.md +16 -18
  92. package/skills/frontend-verification/SKILL.md +16 -13
  93. package/skills/frontend-verification/references/verification-checklist.md +18 -30
  94. package/skills/grill-with-docs/SKILL.md +44 -52
  95. package/skills/grill-with-docs/adr-format.md +37 -26
  96. package/skills/grill-with-docs/context-format.md +18 -26
  97. package/skills/loop-agent/SKILL.md +28 -112
  98. package/skills/loop-agent/references/command-reference.md +9 -3
  99. package/skills/loop-agent/references/harness-policy.md +3 -3
  100. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  101. package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
  102. package/skills/loop-agent/references/task-workflow.md +2 -0
  103. package/skills/systematic-debugging/SKILL.md +20 -4
  104. package/skills/test-driven-development/SKILL.md +10 -3
  105. package/dist/worker/console/static/assets/index-CUDke82y.js +0 -18
  106. package/dist/worker/console/static/assets/index-wSEksVSO.css +0 -1
  107. /package/docs/{harness-methodology-tdd.md → governance/harness-methodology-tdd.md} +0 -0
  108. /package/docs/{harness-methodology-verification.md → governance/harness-methodology-verification.md} +0 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Read-only skill-resolution / context-usage aggregation over recent DAG runs.
3
+ *
4
+ * Terminology: this reports resolution / truncation / unresolved-reference
5
+ * distribution and correlated node outcomes. It does NOT claim skill "execution"
6
+ * or model compliance rates — snapshots prove parse+inject only.
7
+ */
8
+ import { readdir, readFile, stat } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "../../workflows/dag/skill-snapshot.js";
11
+ const LIFECYCLES = ["active", "paused", "completed"];
12
+ function emptySkillCounts() {
13
+ return {
14
+ declared: 0,
15
+ resolved: 0,
16
+ missing: 0,
17
+ error: 0,
18
+ entryTruncated: 0,
19
+ referenceTruncated: 0,
20
+ unresolvedByReason: {},
21
+ };
22
+ }
23
+ function emptyNodeOutcomes() {
24
+ return {
25
+ FINISHED: 0,
26
+ ERROR: 0,
27
+ SKIPPED: 0,
28
+ PENDING: 0,
29
+ RUNNING: 0,
30
+ other: 0,
31
+ failureCategories: {},
32
+ };
33
+ }
34
+ function bumpReason(map, reason) {
35
+ map[reason] = (map[reason] ?? 0) + 1;
36
+ }
37
+ function ensureSkill(skills, name) {
38
+ if (!skills[name])
39
+ skills[name] = emptySkillCounts();
40
+ return skills[name];
41
+ }
42
+ export function parseStatsContextArgs(args) {
43
+ let last = 50;
44
+ let json = false;
45
+ for (let i = 0; i < args.length; i += 1) {
46
+ const token = args[i];
47
+ if (token === "--json" || token === "--json=true") {
48
+ json = true;
49
+ continue;
50
+ }
51
+ if (token.startsWith("--json=")) {
52
+ json = token.slice("--json=".length) === "true";
53
+ continue;
54
+ }
55
+ if (token === "--last") {
56
+ const raw = args[i + 1];
57
+ if (!raw || raw.startsWith("-")) {
58
+ throw new Error("usage: stats context [--last <n>] [--json]");
59
+ }
60
+ const parsed = Number.parseInt(raw, 10);
61
+ if (!Number.isFinite(parsed) || parsed < 1) {
62
+ throw new Error(`--last must be a positive integer, got: ${raw}`);
63
+ }
64
+ last = parsed;
65
+ i += 1;
66
+ continue;
67
+ }
68
+ if (token.startsWith("--last=")) {
69
+ const raw = token.slice("--last=".length);
70
+ const parsed = Number.parseInt(raw, 10);
71
+ if (!Number.isFinite(parsed) || parsed < 1) {
72
+ throw new Error(`--last must be a positive integer, got: ${raw}`);
73
+ }
74
+ last = parsed;
75
+ continue;
76
+ }
77
+ throw new Error(`unknown argument: ${token}\nusage: stats context [--last <n>] [--json]`);
78
+ }
79
+ return { last, json };
80
+ }
81
+ async function listRecentDagRuns(repoRoot, last) {
82
+ const matches = [];
83
+ for (const lifecycle of LIFECYCLES) {
84
+ const lifecycleDir = path.join(repoRoot, ".harness", "dag-runs", lifecycle);
85
+ let entries = [];
86
+ try {
87
+ entries = await readdir(lifecycleDir);
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ for (const name of entries) {
93
+ if (!name || name === ".gitkeep")
94
+ continue;
95
+ const runDir = path.join(lifecycleDir, name);
96
+ let st;
97
+ try {
98
+ st = await stat(runDir);
99
+ }
100
+ catch {
101
+ continue;
102
+ }
103
+ if (!st.isDirectory())
104
+ continue;
105
+ matches.push({
106
+ runId: name,
107
+ lifecycle,
108
+ runDir,
109
+ mtimeMs: st.mtimeMs,
110
+ });
111
+ }
112
+ }
113
+ matches.sort((a, b) => b.mtimeMs - a.mtimeMs);
114
+ return matches.slice(0, last);
115
+ }
116
+ async function readOptionalJson(filePath) {
117
+ try {
118
+ return JSON.parse(await readFile(filePath, "utf8"));
119
+ }
120
+ catch {
121
+ return undefined;
122
+ }
123
+ }
124
+ function ingestSnapshot(skills, snapshot) {
125
+ for (const profile of snapshot.profiles) {
126
+ for (const skillName of profile.skills) {
127
+ ensureSkill(skills, skillName).declared += 1;
128
+ }
129
+ for (const instruction of profile.resolvedInstructions) {
130
+ const row = ensureSkill(skills, instruction.name);
131
+ if (instruction.resolution === "resolved")
132
+ row.resolved += 1;
133
+ else if (instruction.resolution === "missing")
134
+ row.missing += 1;
135
+ else if (instruction.resolution === "error")
136
+ row.error += 1;
137
+ if (instruction.truncated)
138
+ row.entryTruncated += 1;
139
+ for (const ref of instruction.references ?? []) {
140
+ if (ref.truncated)
141
+ row.referenceTruncated += 1;
142
+ }
143
+ for (const unresolved of instruction.unresolvedReferences ?? []) {
144
+ bumpReason(row.unresolvedByReason, unresolved.reason);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ function ingestNodeOutcomes(outcomes, state) {
150
+ for (const node of Object.values(state.nodes ?? {})) {
151
+ const status = node.status;
152
+ if (status === "FINISHED")
153
+ outcomes.FINISHED += 1;
154
+ else if (status === "ERROR")
155
+ outcomes.ERROR += 1;
156
+ else if (status === "SKIPPED")
157
+ outcomes.SKIPPED += 1;
158
+ else if (status === "PENDING")
159
+ outcomes.PENDING += 1;
160
+ else if (status === "RUNNING")
161
+ outcomes.RUNNING += 1;
162
+ else
163
+ outcomes.other += 1;
164
+ if (node.failureCategory) {
165
+ bumpReason(outcomes.failureCategories, node.failureCategory);
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * Pure-ish builder: filesystem reads only; never writes `.harness/**`.
171
+ */
172
+ export async function buildContextUsageReport(repoRoot, options = {}) {
173
+ const last = options.last ?? 50;
174
+ const candidates = await listRecentDagRuns(repoRoot, last);
175
+ const skills = {};
176
+ const nodeOutcomes = emptyNodeOutcomes();
177
+ const byLifecycle = {
178
+ active: 0,
179
+ paused: 0,
180
+ completed: 0,
181
+ };
182
+ const findings = [];
183
+ const notes = [
184
+ "skill-resolution telemetry only; does not claim model compliance or skill execution rates",
185
+ "corrupted snapshots are reported as findings and are not silently skipped",
186
+ ];
187
+ for (const candidate of candidates) {
188
+ byLifecycle[candidate.lifecycle] += 1;
189
+ const state = await readOptionalJson(path.join(candidate.runDir, "state.json"));
190
+ const runJson = await readOptionalJson(path.join(candidate.runDir, "run.json"));
191
+ const finding = {
192
+ runId: candidate.runId,
193
+ lifecycle: candidate.lifecycle,
194
+ mtimeMs: candidate.mtimeMs,
195
+ runStatus: state?.status,
196
+ contextPolicyId: runJson?.defaults?.contextPolicyId,
197
+ snapshot: "missing",
198
+ };
199
+ if (state)
200
+ ingestNodeOutcomes(nodeOutcomes, state);
201
+ const ref = state?.skillSnapshotRef;
202
+ if (!ref) {
203
+ findings.push(finding);
204
+ continue;
205
+ }
206
+ try {
207
+ const snapshot = await readSkillSnapshot(candidate.runDir, ref);
208
+ finding.snapshot = "ok";
209
+ ingestSnapshot(skills, snapshot);
210
+ }
211
+ catch (error) {
212
+ finding.snapshot = "corrupted";
213
+ finding.snapshotError = isDagSkillSnapshotIntegrityError(error)
214
+ ? error.message
215
+ : error instanceof Error
216
+ ? error.message
217
+ : String(error);
218
+ }
219
+ findings.push(finding);
220
+ }
221
+ const corrupted = findings.filter((f) => f.snapshot === "corrupted").length;
222
+ return {
223
+ schemaVersion: 1,
224
+ command: "stats.context",
225
+ ok: corrupted === 0,
226
+ scannedRuns: candidates.length,
227
+ requestedLast: last,
228
+ byLifecycle,
229
+ skills,
230
+ nodeOutcomes,
231
+ findings,
232
+ notes,
233
+ };
234
+ }
235
+ export function formatContextUsageReportHuman(report) {
236
+ const lines = [];
237
+ lines.push(`stats context (last ${report.requestedLast}, scanned ${report.scannedRuns})`);
238
+ lines.push(`lifecycle: active=${report.byLifecycle.active} paused=${report.byLifecycle.paused} completed=${report.byLifecycle.completed}`);
239
+ lines.push(`nodes: FINISHED=${report.nodeOutcomes.FINISHED} ERROR=${report.nodeOutcomes.ERROR} SKIPPED=${report.nodeOutcomes.SKIPPED} PENDING=${report.nodeOutcomes.PENDING} RUNNING=${report.nodeOutcomes.RUNNING}`);
240
+ const skillNames = Object.keys(report.skills).sort();
241
+ if (skillNames.length === 0) {
242
+ lines.push("skills: (none resolved in scanned snapshots)");
243
+ }
244
+ else {
245
+ lines.push("skills:");
246
+ for (const name of skillNames) {
247
+ const s = report.skills[name];
248
+ const budget = s.unresolvedByReason.budget ?? 0;
249
+ lines.push(` ${name}: declared=${s.declared} resolved=${s.resolved} missing=${s.missing} error=${s.error} entryTrunc=${s.entryTruncated} refTrunc=${s.referenceTruncated} budgetSkip=${budget}`);
250
+ }
251
+ }
252
+ const corrupted = report.findings.filter((f) => f.snapshot === "corrupted");
253
+ if (corrupted.length > 0) {
254
+ lines.push(`corrupted snapshots: ${corrupted.length}`);
255
+ for (const finding of corrupted.slice(0, 5)) {
256
+ lines.push(` ${finding.lifecycle}/${finding.runId}: ${finding.snapshotError ?? "unknown"}`);
257
+ }
258
+ }
259
+ for (const note of report.notes) {
260
+ lines.push(`note: ${note}`);
261
+ }
262
+ return lines.join("\n");
263
+ }
@@ -68,6 +68,15 @@ function buildNextSteps(taskId, outputPath, cwd) {
68
68
  `loop-agent dag run-task ${taskId} --execute --cwd ${cwd}`,
69
69
  ];
70
70
  }
71
+ function buildBasicValidationLevel(outputPath) {
72
+ return {
73
+ level: "basic",
74
+ strictGovernance: false,
75
+ strictGovernanceValidationRequiredSeparately: true,
76
+ standaloneStrictGovernanceCommand: `loop-agent dag validate --dag ${outputPath} --strict-models --strict-governance`,
77
+ note: "Internal generation validation is basic only; standalone strict governance validation must be confirmed separately.",
78
+ };
79
+ }
71
80
  function isBroadWriteSetEntryForPacket(entry) {
72
81
  const normalized = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
73
82
  if (!normalized || normalized === "." || normalized === "./")
@@ -168,6 +177,7 @@ async function buildReviewPacket(input) {
168
177
  ? (task.decisionGate.mode ?? "record-only")
169
178
  : "disabled",
170
179
  })),
180
+ validation: buildBasicValidationLevel(input.dagPath),
171
181
  expectedVerification: shellVerification.flatMap((entry) => entry.commands),
172
182
  };
173
183
  }
@@ -281,10 +291,12 @@ export async function generateTaskDagUseCase(input) {
281
291
  profileRouting,
282
292
  governanceProfile,
283
293
  });
294
+ const validation = buildBasicValidationLevel(outputPath);
295
+ const message = "DAG draft created with basic validation; standalone strict governance validation still requires separate confirmation.";
284
296
  if (!shouldRunExecution(parsed)) {
285
297
  return {
286
- mode: "generate+validate",
287
- message: "DAG draft created",
298
+ mode: "generate+basic-validate",
299
+ message,
288
300
  ok: true,
289
301
  taskId: initResult.taskId,
290
302
  outputPath,
@@ -296,6 +308,7 @@ export async function generateTaskDagUseCase(input) {
296
308
  governanceProfile,
297
309
  profileRouting,
298
310
  reviewPacket,
311
+ validation,
299
312
  warnings: validateSummary.warnings,
300
313
  next: buildNextSteps(parsed.taskId, outputPath, parsed.cwd),
301
314
  };
@@ -327,7 +340,7 @@ export async function generateTaskDagUseCase(input) {
327
340
  });
328
341
  return {
329
342
  mode: resolveExecutionMode(parsed),
330
- message: "DAG draft created",
343
+ message,
331
344
  ok: true,
332
345
  taskId: initResult.taskId,
333
346
  outputPath,
@@ -338,6 +351,7 @@ export async function generateTaskDagUseCase(input) {
338
351
  governanceProfile,
339
352
  profileRouting,
340
353
  reviewPacket,
354
+ validation,
341
355
  warnings: validateSummary.warnings,
342
356
  run: runSummary,
343
357
  };
@@ -184,9 +184,9 @@ export const COMMAND_DEFINITIONS = [
184
184
  adapter: "required",
185
185
  tier: "operator",
186
186
  intent: "Check local loop-agent runtime health.",
187
- usage: "doctor",
188
- handler: async ({ adapter, repoRoot }) => {
189
- await runDoctor(adapter, repoRoot);
187
+ usage: "doctor [--json] [--context]",
188
+ handler: async ({ adapter, repoRoot, rest }) => {
189
+ await runDoctor(adapter, repoRoot, rest.filter((arg) => Boolean(arg)));
190
190
  },
191
191
  },
192
192
  {
@@ -345,10 +345,11 @@ export const COMMAND_DEFINITIONS = [
345
345
  name: "stats",
346
346
  adapter: "required",
347
347
  tier: "operator",
348
- intent: "Inspect historical step duration statistics.",
349
- usage: "stats",
350
- handler: async ({ repoRoot }) => {
351
- await runStats(repoRoot);
348
+ intent: "Inspect historical step duration statistics, or skill-resolution context usage.",
349
+ usage: "stats | stats context [--last <n>] [--json]",
350
+ subcommands: ["context"],
351
+ handler: async ({ repoRoot, subcommand, rest }) => {
352
+ await runStats(repoRoot, [subcommand, ...rest].filter((arg) => Boolean(arg)));
352
353
  },
353
354
  },
354
355
  {
@@ -92,25 +92,27 @@ function commandChain(command) {
92
92
  }
93
93
  return names;
94
94
  }
95
- async function resolveDispatchContext(command, defaultRepoRoot) {
96
- const options = collectGlobalOptions(command);
97
- const repoRoot = path.resolve(options.repoRoot ?? defaultRepoRoot);
95
+ async function dispatchRegisteredCommand(commandObject, defaultRepoRoot, rawArgs) {
96
+ const chain = commandChain(commandObject);
97
+ const command = chain[0] ?? "";
98
+ const [subcommand, ...rest] = chain.length <= 1 ? rawArgs : [chain[1], ...chain.slice(2), ...rawArgs];
99
+ const options = collectGlobalOptions(commandObject);
100
+ const quiet = (command === "doctor" &&
101
+ [...rest, ...rawArgs].some((token) => token === "--json")) ||
102
+ (command === "stats" &&
103
+ subcommand === "context" &&
104
+ [...rest, ...rawArgs].some((token) => token === "--json"));
105
+ const repoRoot = resolveRepoRootOnly(commandObject, defaultRepoRoot, {
106
+ quiet,
107
+ });
98
108
  const adapter = await resolveAdapter(repoRoot, options.adapter);
99
- if (options.adapter)
109
+ if (options.adapter && !quiet)
100
110
  console.log(`[loop-agent] adapter: ${adapter.name}`);
101
- if (options.repoRoot)
102
- console.log(`[loop-agent] repo root: ${repoRoot}`);
103
- return {
111
+ const ctx = {
104
112
  adapter,
105
113
  repoRoot,
106
114
  explicitWorktree: options.worktree,
107
115
  };
108
- }
109
- async function dispatchRegisteredCommand(commandObject, defaultRepoRoot, rawArgs) {
110
- const chain = commandChain(commandObject);
111
- const command = chain[0] ?? "";
112
- const [subcommand, ...rest] = chain.length <= 1 ? rawArgs : [chain[1], ...chain.slice(2), ...rawArgs];
113
- const ctx = await resolveDispatchContext(commandObject, defaultRepoRoot);
114
116
  await runCommanderAction(ctx, command, subcommand, rest);
115
117
  }
116
118
  async function runCommanderAction(ctx, command, subcommand, rest) {
@@ -119,7 +121,7 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
119
121
  await runInspect(ctx.adapter, ctx.repoRoot);
120
122
  return;
121
123
  case "doctor":
122
- await runDoctor(ctx.adapter, ctx.repoRoot);
124
+ await runDoctor(ctx.adapter, ctx.repoRoot, compactArgs([subcommand, ...rest]));
123
125
  return;
124
126
  case "docs":
125
127
  if (subcommand === "audit") {
@@ -184,7 +186,7 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
184
186
  await runCloseout(ctx.repoRoot, compactArgs([subcommand, ...rest]));
185
187
  return;
186
188
  case "stats":
187
- await runStats(ctx.repoRoot);
189
+ await runStats(ctx.repoRoot, compactArgs([subcommand, ...rest]));
188
190
  return;
189
191
  case "plan":
190
192
  if (subcommand === "list" || subcommand === undefined) {