@tea-agent/loop-agent 0.22.0 → 0.24.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/AGENTS.md +42 -108
- package/CHANGELOG.md +85 -0
- package/README.md +8 -5
- package/bin/agent-worker.js +0 -0
- package/dist/application/context-usage/skill-resolution-stats.js +263 -0
- package/dist/application/dag/generate-task-dag.js +17 -3
- package/dist/cli/command-definitions.js +8 -7
- package/dist/cli/program.js +17 -15
- package/dist/commands/doctor.js +269 -18
- package/dist/commands/init.js +101 -86
- package/dist/commands/stats.js +40 -11
- package/dist/executors/shell-executor.js +20 -7
- package/dist/shared/operator/capabilities.js +486 -3
- package/dist/worker/console/app-data.js +6 -0
- package/dist/worker/console/chat/artifact-card.js +23 -0
- package/dist/worker/console/chat/chat-event-store.js +495 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +37 -0
- package/dist/worker/console/chat/instruction-skills.js +217 -0
- package/dist/worker/console/chat/interview-adapter.js +136 -0
- package/dist/worker/console/chat/model-resolver.js +106 -0
- package/dist/worker/console/chat/operation-card.js +23 -0
- package/dist/worker/console/chat/pi-console-config.js +158 -0
- package/dist/worker/console/chat/pi-runtime.js +1143 -0
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +116 -0
- package/dist/worker/console/chat/resource-loader.js +67 -0
- package/dist/worker/console/chat/routes.js +1646 -0
- package/dist/worker/console/chat/runtime-context.js +24 -0
- package/dist/worker/console/chat/runtime-selection.js +37 -0
- package/dist/worker/console/chat/session-store.js +437 -0
- package/dist/worker/console/chat/shortcuts.js +15 -0
- package/dist/worker/console/chat/tool-adapter.js +125 -0
- package/dist/worker/console/chat/tools.js +195 -0
- package/dist/worker/console/chat/usage.js +37 -0
- package/dist/worker/console/chat/workspace-landing.js +56 -0
- package/dist/worker/console/dag-confirmation.js +42 -8
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/operation-runner.js +6 -2
- package/dist/worker/console/operation-sse.js +26 -0
- package/dist/worker/console/operator-actions.js +420 -7
- package/dist/worker/console/server.js +68 -1
- package/dist/worker/console/static/assets/index-BTbrEHnO.css +1 -0
- package/dist/worker/console/static/assets/index-D9qLevoP.js +27 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/feature/profile-schema.js +1 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +9 -5
- package/dist/workflows/dag/backend-test-result-contract.js +229 -0
- package/dist/workflows/dag/frontend-lint-baseline.js +4 -4
- package/dist/workflows/dag/init-hybrid.js +71 -22
- package/dist/workflows/dag/node-execution.js +38 -1
- package/dist/workflows/dag/output-protocol.js +89 -0
- package/dist/workflows/dag/prompt.js +35 -1
- package/dist/workflows/dag/recovery-recommendation.js +45 -0
- package/dist/workflows/dag/report.js +28 -1
- package/dist/workflows/dag/rerun-task.js +1 -1
- package/dist/workflows/dag/scheduler.js +9 -0
- package/dist/workflows/dag/types.js +12 -0
- package/dist/workflows/dag/validate.js +55 -0
- package/docs/README.md +73 -156
- package/docs/architecture/README.md +7 -6
- package/docs/architecture/dag-execution.md +2 -2
- package/docs/architecture/evolution.md +16 -14
- package/docs/architecture/system-overview.md +1 -1
- package/docs/architecture/worker-and-feature.md +3 -3
- package/docs/governance/README.md +15 -0
- package/docs/{harness-methodology-debugging.md → governance/harness-methodology-debugging.md} +27 -3
- package/docs/init-surface.manifest.json +22 -4
- package/docs/operations/README.md +12 -0
- package/docs/{local-development-environment.md → operations/local-development-environment.md} +1 -1
- package/docs/skills/vetted-skill-registry.md +23 -3
- package/docs/templates/README.md +55 -0
- package/docs/templates/backend-test-dag.json +2 -2
- package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
- package/docs/templates/hybrid-dag.json +1 -1
- package/docs/templates/progress-log.md +9 -2
- package/harness.json +4 -4
- package/package.json +5 -5
- package/scripts/kb-bootstrap-init-skeleton.sh +2 -2
- package/skills/agent-worker/SKILL.md +1 -1
- package/skills/grill-with-docs/SKILL.md +44 -52
- package/skills/grill-with-docs/adr-format.md +37 -26
- package/skills/grill-with-docs/context-format.md +18 -26
- package/skills/loop-agent/SKILL.md +28 -112
- package/skills/loop-agent/references/command-reference.md +9 -3
- package/skills/loop-agent/references/harness-policy.md +3 -3
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +1 -1
- package/skills/loop-agent/references/task-workflow.md +2 -0
- package/skills/systematic-debugging/SKILL.md +20 -4
- package/skills/test-driven-development/SKILL.md +10 -3
- package/dist/worker/console/static/assets/index-CUDke82y.js +0 -18
- package/dist/worker/console/static/assets/index-wSEksVSO.css +0 -1
- /package/docs/{harness-methodology-tdd.md → governance/harness-methodology-tdd.md} +0 -0
- /package/docs/{harness-methodology-verification.md → governance/harness-methodology-verification.md} +0 -0
|
@@ -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
|
-
|
|
351
|
-
|
|
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
|
{
|
package/dist/cli/program.js
CHANGED
|
@@ -92,25 +92,27 @@ function commandChain(command) {
|
|
|
92
92
|
}
|
|
93
93
|
return names;
|
|
94
94
|
}
|
|
95
|
-
async function
|
|
96
|
-
const
|
|
97
|
-
const
|
|
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
|
-
|
|
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) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,32 +1,283 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { checkPiAvailability, resolvePiBackend } from "../executors/pi-executor.js";
|
|
5
|
+
import { checkPiSdkAvailability } from "../executors/pi-sdk-executor.js";
|
|
6
|
+
import { getContextPolicy, } from "../workflows/dag/context-policy.js";
|
|
7
|
+
import { DEFAULT_SKILLS_BY_ROLE, } from "../workflows/dag/skills.js";
|
|
8
|
+
import { resolveDagSkillInstructions, } from "../workflows/dag/skill-instructions.js";
|
|
9
|
+
const ENTRYPOINT_KEYS = [
|
|
10
|
+
"readme",
|
|
11
|
+
"agents",
|
|
12
|
+
"governanceIndex",
|
|
13
|
+
"principles",
|
|
14
|
+
"workflow",
|
|
15
|
+
"verificationMatrix",
|
|
16
|
+
];
|
|
17
|
+
const CONTEXT_AUDIT_ROLES = ["planner", "supervisor", "closeout"];
|
|
18
|
+
/** Parse `--flag` / `--flag=value` boolean flags from a raw argv token list. */
|
|
19
|
+
export function parseDoctorArgs(args) {
|
|
20
|
+
let json = false;
|
|
21
|
+
let context = false;
|
|
22
|
+
for (const token of args) {
|
|
23
|
+
if (token === "--json" || token === "--json=true")
|
|
24
|
+
json = true;
|
|
25
|
+
else if (token === "--context" || token === "--context=true")
|
|
26
|
+
context = true;
|
|
27
|
+
else if (token.startsWith("--context="))
|
|
28
|
+
context = token.slice("--context=".length) === "true";
|
|
29
|
+
else if (token.startsWith("--json="))
|
|
30
|
+
json = token.slice("--json=".length) === "true";
|
|
31
|
+
}
|
|
32
|
+
return { json, context };
|
|
33
|
+
}
|
|
34
|
+
function summarize(checks) {
|
|
35
|
+
let errors = 0;
|
|
36
|
+
let warnings = 0;
|
|
37
|
+
let infos = 0;
|
|
38
|
+
for (const check of checks) {
|
|
39
|
+
if (check.status === "error")
|
|
40
|
+
errors += 1;
|
|
41
|
+
else if (check.status === "warning")
|
|
42
|
+
warnings += 1;
|
|
43
|
+
else
|
|
44
|
+
infos += 1;
|
|
45
|
+
}
|
|
46
|
+
return { errors, warnings, infos };
|
|
47
|
+
}
|
|
48
|
+
async function collectManifestChecks(adapter, repoRoot) {
|
|
49
|
+
const checks = [];
|
|
6
50
|
const manifest = await adapter.loadHarnessManifest(repoRoot);
|
|
7
51
|
const requiredPaths = [];
|
|
8
|
-
|
|
52
|
+
const entrypointLabels = [];
|
|
53
|
+
for (const key of ENTRYPOINT_KEYS) {
|
|
9
54
|
const value = manifest.entrypoints[key];
|
|
10
|
-
if (value)
|
|
55
|
+
if (value) {
|
|
11
56
|
requiredPaths.push(value);
|
|
57
|
+
entrypointLabels.push(`${key}=${value}`);
|
|
58
|
+
}
|
|
12
59
|
}
|
|
13
60
|
const checkRepoScript = manifest.scripts?.checkRepo;
|
|
14
|
-
if (checkRepoScript)
|
|
61
|
+
if (checkRepoScript) {
|
|
15
62
|
requiredPaths.push(checkRepoScript);
|
|
16
|
-
|
|
17
|
-
requiredPaths.push(
|
|
63
|
+
}
|
|
64
|
+
requiredPaths.push(".harness/prompts/analyze.md");
|
|
65
|
+
requiredPaths.push(".harness/prompts/plan.md");
|
|
18
66
|
for (const rel of requiredPaths) {
|
|
19
|
-
|
|
67
|
+
try {
|
|
68
|
+
await access(path.join(repoRoot, rel));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
checks.push({
|
|
72
|
+
id: `manifest-path:${rel}`,
|
|
73
|
+
status: "error",
|
|
74
|
+
message: `required harness path missing: ${rel}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
20
77
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
78
|
+
checks.push({
|
|
79
|
+
id: "manifest-entrypoints",
|
|
80
|
+
status: "info",
|
|
81
|
+
message: `harness manifest entrypoints resolved`,
|
|
82
|
+
detail: { entrypoints: entrypointLabels },
|
|
83
|
+
});
|
|
84
|
+
return checks;
|
|
85
|
+
}
|
|
86
|
+
function collectExecutorChecks(piOk, piDetail, backendMode, sdkOk, sdkDetail) {
|
|
87
|
+
const checks = [];
|
|
88
|
+
if (!piOk) {
|
|
89
|
+
checks.push({
|
|
90
|
+
id: "executor:pi-cli",
|
|
91
|
+
status: "error",
|
|
92
|
+
message: `pi unavailable: ${piDetail}`,
|
|
93
|
+
});
|
|
94
|
+
return checks;
|
|
95
|
+
}
|
|
96
|
+
checks.push({
|
|
97
|
+
id: "executor:pi-cli",
|
|
98
|
+
status: "info",
|
|
99
|
+
message: `pi backend=${backendMode} cli=ok sdk=${sdkOk ? "ok" : "unavailable"}`,
|
|
100
|
+
});
|
|
101
|
+
if (backendMode === "sdk-first" && !sdkOk) {
|
|
102
|
+
checks.push({
|
|
103
|
+
id: "executor:pi-sdk",
|
|
104
|
+
status: "warning",
|
|
105
|
+
message: `CODE_AGENT_PI_BACKEND=sdk-first but pi SDK unavailable (${sdkDetail}); steps will fall back to CLI`,
|
|
106
|
+
});
|
|
24
107
|
}
|
|
108
|
+
return checks;
|
|
109
|
+
}
|
|
110
|
+
async function collectContextAudit(repoRoot) {
|
|
111
|
+
const policyId = "baseline-v1";
|
|
112
|
+
const policy = getContextPolicy(policyId);
|
|
113
|
+
const manifest = policy.toManifest();
|
|
114
|
+
const roles = [];
|
|
115
|
+
for (const role of CONTEXT_AUDIT_ROLES) {
|
|
116
|
+
const skillNames = DEFAULT_SKILLS_BY_ROLE[role];
|
|
117
|
+
const budget = policy.resolveSkillInstructionBudget({
|
|
118
|
+
role,
|
|
119
|
+
executor: "pi",
|
|
120
|
+
});
|
|
121
|
+
const resolved = await resolveDagSkillInstructions([...skillNames], {
|
|
122
|
+
cwd: repoRoot,
|
|
123
|
+
env: {},
|
|
124
|
+
homeDir: os.homedir(),
|
|
125
|
+
perSkillMaxChars: budget.perSkillMaxChars,
|
|
126
|
+
totalMaxChars: budget.totalMaxChars,
|
|
127
|
+
includeLearnedPatterns: budget.includeLearnedPatterns,
|
|
128
|
+
});
|
|
129
|
+
const skills = resolved.map((entry) => toContextAuditSkill(entry));
|
|
130
|
+
roles.push({ role, skills });
|
|
131
|
+
}
|
|
132
|
+
return { policy: manifest, roles };
|
|
133
|
+
}
|
|
134
|
+
function toContextAuditSkill(entry) {
|
|
135
|
+
return {
|
|
136
|
+
name: entry.name,
|
|
137
|
+
resolution: entry.resolution,
|
|
138
|
+
entryTruncated: Boolean(entry.truncated),
|
|
139
|
+
...(entry.references
|
|
140
|
+
? {
|
|
141
|
+
references: entry.references.map((ref) => ({
|
|
142
|
+
name: ref.name,
|
|
143
|
+
sha256: ref.sha256,
|
|
144
|
+
bytes: ref.bytes,
|
|
145
|
+
truncated: Boolean(ref.truncated),
|
|
146
|
+
})),
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
149
|
+
...(entry.unresolvedReferences
|
|
150
|
+
? { unresolvedReferences: entry.unresolvedReferences }
|
|
151
|
+
: {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function contextAuditToChecks(audit) {
|
|
155
|
+
const checks = [];
|
|
156
|
+
for (const roleEntry of audit.roles) {
|
|
157
|
+
for (const skill of roleEntry.skills) {
|
|
158
|
+
if (skill.resolution === "missing" || skill.resolution === "error") {
|
|
159
|
+
checks.push({
|
|
160
|
+
id: `context:${roleEntry.role}:${skill.name}:resolution`,
|
|
161
|
+
status: "error",
|
|
162
|
+
message: `role ${roleEntry.role} skill ${skill.name} did not resolve (${skill.resolution})`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (skill.entryTruncated) {
|
|
166
|
+
checks.push({
|
|
167
|
+
id: `context:${roleEntry.role}:${skill.name}:entry-truncated`,
|
|
168
|
+
status: "warning",
|
|
169
|
+
message: `role ${roleEntry.role} skill ${skill.name} entry truncated under default budget`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
for (const ref of skill.references ?? []) {
|
|
173
|
+
if (ref.truncated) {
|
|
174
|
+
checks.push({
|
|
175
|
+
id: `context:${roleEntry.role}:${skill.name}:ref:${ref.name}:truncated`,
|
|
176
|
+
status: "warning",
|
|
177
|
+
message: `role ${roleEntry.role} skill ${skill.name} reference ${ref.name} excerpt truncated under default budget`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const unresolved of skill.unresolvedReferences ?? []) {
|
|
182
|
+
if (unresolved.reason === "budget") {
|
|
183
|
+
checks.push({
|
|
184
|
+
id: `context:${roleEntry.role}:${skill.name}:ref:${unresolved.name ?? unresolved.path}:budget`,
|
|
185
|
+
status: "error",
|
|
186
|
+
message: `role ${roleEntry.role} skill ${skill.name} reference ${unresolved.name ?? unresolved.path} skipped: reason=budget`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else if (unresolved.reason === "missing" ||
|
|
190
|
+
unresolved.reason === "read-error" ||
|
|
191
|
+
unresolved.reason === "cycle") {
|
|
192
|
+
checks.push({
|
|
193
|
+
id: `context:${roleEntry.role}:${skill.name}:ref:${unresolved.name ?? unresolved.path}:${unresolved.reason}`,
|
|
194
|
+
status: "error",
|
|
195
|
+
message: `role ${roleEntry.role} skill ${skill.name} reference ${unresolved.name ?? unresolved.path} unresolved: reason=${unresolved.reason}`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return checks;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Build the typed DoctorReport without side effects. This is the single fact
|
|
205
|
+
* source shared by both the human-readable and JSON renderers in runDoctor.
|
|
206
|
+
*/
|
|
207
|
+
export async function buildDoctorReport(adapter, repoRoot, options = {}) {
|
|
208
|
+
const checks = [];
|
|
209
|
+
checks.push(...(await collectManifestChecks(adapter, repoRoot)));
|
|
210
|
+
const pi = checkPiAvailability();
|
|
25
211
|
const backendMode = resolvePiBackend();
|
|
26
212
|
const sdk = await checkPiSdkAvailability(repoRoot);
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
213
|
+
checks.push(...collectExecutorChecks(pi.ok, pi.detail, backendMode, sdk.ok, sdk.detail));
|
|
214
|
+
if (options.context) {
|
|
215
|
+
const audit = await collectContextAudit(repoRoot);
|
|
216
|
+
checks.push(...contextAuditToChecks(audit));
|
|
217
|
+
return {
|
|
218
|
+
schemaVersion: 1,
|
|
219
|
+
ok: !checks.some((check) => check.status === "error"),
|
|
220
|
+
command: "doctor",
|
|
221
|
+
checks,
|
|
222
|
+
summary: summarize(checks),
|
|
223
|
+
contextAudit: audit,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
schemaVersion: 1,
|
|
228
|
+
ok: !checks.some((check) => check.status === "error"),
|
|
229
|
+
command: "doctor",
|
|
230
|
+
checks,
|
|
231
|
+
summary: summarize(checks),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function renderHumanReport(report) {
|
|
235
|
+
for (const check of report.checks) {
|
|
236
|
+
const tag = check.status === "error"
|
|
237
|
+
? "ERROR"
|
|
238
|
+
: check.status === "warning"
|
|
239
|
+
? "warn"
|
|
240
|
+
: "info";
|
|
241
|
+
const line = check.detail !== undefined
|
|
242
|
+
? `doctor [${tag}] ${check.id}: ${check.message}`
|
|
243
|
+
: `doctor [${tag}] ${check.id}: ${check.message}`;
|
|
244
|
+
if (check.status === "error") {
|
|
245
|
+
console.error(line);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.log(line);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (report.contextAudit) {
|
|
252
|
+
console.log(`doctor context: policy=${report.contextAudit.policy.policyId} roles=${report.contextAudit.roles.map((role) => role.role).join(",")}`);
|
|
253
|
+
for (const role of report.contextAudit.roles) {
|
|
254
|
+
for (const skill of role.skills) {
|
|
255
|
+
const refs = (skill.references ?? [])
|
|
256
|
+
.map((ref) => `${ref.name}${ref.truncated ? "*" : ""}`)
|
|
257
|
+
.join(",");
|
|
258
|
+
console.log(` ${role.role}/${skill.name}: resolution=${skill.resolution} entryTruncated=${skill.entryTruncated}${refs ? ` refs=${refs}` : ""}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
console.log(`doctor: ${report.ok ? "ok" : "errors"} (errors=${report.summary.errors} warnings=${report.summary.warnings} infos=${report.summary.infos})`);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Run the doctor command. Renders a human-readable summary by default, or a
|
|
266
|
+
* single JSON document when `--json` is present. Both renderers consume the
|
|
267
|
+
* same typed DoctorReport. Returns the process exit code (1 on any error
|
|
268
|
+
* finding, 0 otherwise) and sets process.exitCode accordingly.
|
|
269
|
+
*/
|
|
270
|
+
export async function runDoctor(adapter, repoRoot, args = []) {
|
|
271
|
+
const { json, context } = parseDoctorArgs(args);
|
|
272
|
+
const report = await buildDoctorReport(adapter, repoRoot, { context });
|
|
273
|
+
const exitCode = report.ok ? 0 : 1;
|
|
274
|
+
process.exitCode = exitCode;
|
|
275
|
+
if (json) {
|
|
276
|
+
// Machine envelope: stdout must contain only a single JSON document.
|
|
277
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
renderHumanReport(report);
|
|
30
281
|
}
|
|
31
|
-
|
|
282
|
+
return exitCode;
|
|
32
283
|
}
|