@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
@@ -1,32 +1,283 @@
1
- import { access } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { checkPiAvailability, resolvePiBackend } from '../executors/pi-executor.js';
4
- import { checkPiSdkAvailability } from '../executors/pi-sdk-executor.js';
5
- export async function runDoctor(adapter, repoRoot) {
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
- for (const key of ['readme', 'agents', 'governanceIndex', 'principles', 'workflow', 'verificationMatrix']) {
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
- requiredPaths.push('.harness/prompts/analyze.md');
17
- requiredPaths.push('.harness/prompts/plan.md');
63
+ }
64
+ requiredPaths.push(".harness/prompts/analyze.md");
65
+ requiredPaths.push(".harness/prompts/plan.md");
18
66
  for (const rel of requiredPaths) {
19
- await access(path.join(repoRoot, rel));
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
- const pi = checkPiAvailability();
22
- if (!pi.ok) {
23
- throw new Error(`pi unavailable: ${pi.detail}`);
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
- console.log(`doctor: pi backend=${backendMode} cli=${pi.ok ? 'ok' : 'fail'} sdk=${sdk.ok ? 'ok' : 'unavailable'}`);
28
- if (backendMode === 'sdk-first' && !sdk.ok) {
29
- console.error(`doctor warning: CODE_AGENT_PI_BACKEND=sdk-first but pi SDK unavailable (${sdk.detail}); steps will fall back to CLI`);
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
- console.log('doctor ok');
282
+ return exitCode;
32
283
  }