@tea-agent/loop-agent 0.1.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.
Files changed (264) hide show
  1. package/AGENTS.md +121 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +144 -0
  4. package/bin/loop-agent.js +21 -0
  5. package/dist/adapters/aimax.js +91 -0
  6. package/dist/adapters/context.js +32 -0
  7. package/dist/adapters/index.js +28 -0
  8. package/dist/adapters/loop-agent.js +98 -0
  9. package/dist/adapters/types.js +1 -0
  10. package/dist/cli/catalog.js +259 -0
  11. package/dist/cli/help.js +55 -0
  12. package/dist/cli/index.js +3 -0
  13. package/dist/cli/program.js +505 -0
  14. package/dist/cli.js +12 -0
  15. package/dist/commands/closeout.js +13 -0
  16. package/dist/commands/coverage-audit.js +14 -0
  17. package/dist/commands/cursor-prompt.js +222 -0
  18. package/dist/commands/cursor-worker.js +43 -0
  19. package/dist/commands/dag-approve.js +102 -0
  20. package/dist/commands/dag-final-verification.js +76 -0
  21. package/dist/commands/dag-init-hybrid.js +56 -0
  22. package/dist/commands/dag-reconcile-tasks.js +51 -0
  23. package/dist/commands/dag-reject.js +91 -0
  24. package/dist/commands/dag-report.js +177 -0
  25. package/dist/commands/dag-resume.js +34 -0
  26. package/dist/commands/dag-run-task.js +470 -0
  27. package/dist/commands/dag-validate.js +186 -0
  28. package/dist/commands/dag-workflow-compile.js +91 -0
  29. package/dist/commands/dag-workflow-plan.js +130 -0
  30. package/dist/commands/dag-workflow-validate.js +66 -0
  31. package/dist/commands/delegate.js +132 -0
  32. package/dist/commands/docs-archive.js +5 -0
  33. package/dist/commands/docs-audit.js +5 -0
  34. package/dist/commands/doctor.js +50 -0
  35. package/dist/commands/goal.js +92 -0
  36. package/dist/commands/handoff-check.js +5 -0
  37. package/dist/commands/harvest.js +44 -0
  38. package/dist/commands/inspect.js +11 -0
  39. package/dist/commands/instructions.js +195 -0
  40. package/dist/commands/knowledge.js +64 -0
  41. package/dist/commands/loop-benchmark.js +72 -0
  42. package/dist/commands/loop.js +241 -0
  43. package/dist/commands/new-task.js +5 -0
  44. package/dist/commands/pi-prompt.js +181 -0
  45. package/dist/commands/pi-reuse-benchmark.js +153 -0
  46. package/dist/commands/plan-list.js +5 -0
  47. package/dist/commands/promote-run.js +29 -0
  48. package/dist/commands/reference-index.js +16 -0
  49. package/dist/commands/run-dag.js +184 -0
  50. package/dist/commands/spine.js +38 -0
  51. package/dist/commands/stats.js +84 -0
  52. package/dist/commands/status.js +56 -0
  53. package/dist/commands/study-init.js +192 -0
  54. package/dist/commands/workflow.js +259 -0
  55. package/dist/commands/worktree-create.js +31 -0
  56. package/dist/commands/worktree-list.js +5 -0
  57. package/dist/commands/worktree-remove.js +26 -0
  58. package/dist/cursor-worker-entry.js +8 -0
  59. package/dist/executors/config-core.js +55 -0
  60. package/dist/executors/config.js +2 -0
  61. package/dist/executors/cursor-artifacts.js +33 -0
  62. package/dist/executors/cursor-execution-log.js +81 -0
  63. package/dist/executors/cursor-executor-artifacts.js +135 -0
  64. package/dist/executors/cursor-executor.js +468 -0
  65. package/dist/executors/cursor-run.js +115 -0
  66. package/dist/executors/cursor-tool.js +94 -0
  67. package/dist/executors/cursor-worker-client.js +213 -0
  68. package/dist/executors/cursor-worker-protocol.js +18 -0
  69. package/dist/executors/cursor-worker-server.js +54 -0
  70. package/dist/executors/cursor-worker.js +3 -0
  71. package/dist/executors/cursor.js +6 -0
  72. package/dist/executors/dag-cursor-executor.js +88 -0
  73. package/dist/executors/dag-pi-executor.js +322 -0
  74. package/dist/executors/dag-static-executor.js +45 -0
  75. package/dist/executors/dag.js +4 -0
  76. package/dist/executors/index.js +8 -0
  77. package/dist/executors/model-routing.js +60 -0
  78. package/dist/executors/pi-event-serializer.js +43 -0
  79. package/dist/executors/pi-executor.js +606 -0
  80. package/dist/executors/pi-reuse-benchmark.js +316 -0
  81. package/dist/executors/pi-runtime-reuse.js +29 -0
  82. package/dist/executors/pi-sdk-executor.js +255 -0
  83. package/dist/executors/pi-sdk.js +1 -0
  84. package/dist/executors/pi.js +3 -0
  85. package/dist/executors/shell-executor.js +300 -0
  86. package/dist/executors/shell-presets.js +47 -0
  87. package/dist/executors/shell-verification.js +251 -0
  88. package/dist/executors/shell-write-guard.js +126 -0
  89. package/dist/executors/shell.js +3 -0
  90. package/dist/executors/static.js +1 -0
  91. package/dist/governance/checks.js +434 -0
  92. package/dist/governance/harness.js +9 -0
  93. package/dist/governance/index.js +3 -0
  94. package/dist/governance/manifest-types.js +128 -0
  95. package/dist/governance/manifest.js +2 -0
  96. package/dist/governance/path-guard.js +69 -0
  97. package/dist/governance/path-guards.js +2 -0
  98. package/dist/governance/profiles.js +3 -0
  99. package/dist/governance/requirement-coverage.js +425 -0
  100. package/dist/governance/skill-safety.js +135 -0
  101. package/dist/governance/spine-audit.js +152 -0
  102. package/dist/records/closeout.js +2 -0
  103. package/dist/records/harvest.js +236 -0
  104. package/dist/records/index.js +3 -0
  105. package/dist/records/one-shot-runs.js +421 -0
  106. package/dist/records/promotion.js +199 -0
  107. package/dist/shared/artifacts-core.js +88 -0
  108. package/dist/shared/artifacts.js +2 -0
  109. package/dist/shared/context-files.js +32 -0
  110. package/dist/shared/context.js +2 -0
  111. package/dist/shared/copy-dir.js +17 -0
  112. package/dist/shared/git-progress.js +165 -0
  113. package/dist/shared/index.js +5 -0
  114. package/dist/shared/logger.js +23 -0
  115. package/dist/shared/one-shot-prompt-args.js +98 -0
  116. package/dist/shared/path-refs.js +31 -0
  117. package/dist/shared/prompts.js +26 -0
  118. package/dist/shared/reference-context.js +238 -0
  119. package/dist/shared/timeout-policy.js +19 -0
  120. package/dist/shared/timeout.js +1 -0
  121. package/dist/shared/types.js +5 -0
  122. package/dist/task/config-types.js +97 -0
  123. package/dist/task/config.js +2 -0
  124. package/dist/task/delegate.js +220 -0
  125. package/dist/task/goal-audit.js +51 -0
  126. package/dist/task/goal-policy.js +8 -0
  127. package/dist/task/goal.js +3 -0
  128. package/dist/task/ids.js +1 -0
  129. package/dist/task/index.js +9 -0
  130. package/dist/task/lifecycle.js +1 -0
  131. package/dist/task/paths.js +1 -0
  132. package/dist/task/read-model.js +149 -0
  133. package/dist/task/runtime.js +699 -0
  134. package/dist/task/source-state.js +1 -0
  135. package/dist/task/state.js +55 -0
  136. package/dist/task/subagent-guidance.js +1 -0
  137. package/dist/task/workflow-state-types.js +92 -0
  138. package/dist/task/worktree-cleanup.js +140 -0
  139. package/dist/task/worktree.js +171 -0
  140. package/dist/workflows/dag/authoring.js +8 -0
  141. package/dist/workflows/dag/authority-surface.js +138 -0
  142. package/dist/workflows/dag/canvas-observer.js +474 -0
  143. package/dist/workflows/dag/decision-envelope.js +502 -0
  144. package/dist/workflows/dag/decision-evidence.js +153 -0
  145. package/dist/workflows/dag/decision-gates.js +1 -0
  146. package/dist/workflows/dag/executor-registry.js +25 -0
  147. package/dist/workflows/dag/facts.js +4 -0
  148. package/dist/workflows/dag/failure-category.js +111 -0
  149. package/dist/workflows/dag/final-verification.js +180 -0
  150. package/dist/workflows/dag/governance-constants.js +5 -0
  151. package/dist/workflows/dag/governance-profile.js +405 -0
  152. package/dist/workflows/dag/index.js +6 -0
  153. package/dist/workflows/dag/init-hybrid.js +855 -0
  154. package/dist/workflows/dag/knowledge-curator.js +162 -0
  155. package/dist/workflows/dag/lifecycle.js +484 -0
  156. package/dist/workflows/dag/prompt-source.js +88 -0
  157. package/dist/workflows/dag/prompt.js +130 -0
  158. package/dist/workflows/dag/reconcile-tasks.js +404 -0
  159. package/dist/workflows/dag/recovery-recommendation.js +226 -0
  160. package/dist/workflows/dag/repair-artifact.js +136 -0
  161. package/dist/workflows/dag/report.js +1019 -0
  162. package/dist/workflows/dag/runner.js +1677 -0
  163. package/dist/workflows/dag/runtime.js +5 -0
  164. package/dist/workflows/dag/skill-instructions.js +471 -0
  165. package/dist/workflows/dag/skills.js +41 -0
  166. package/dist/workflows/dag/spec.js +3 -0
  167. package/dist/workflows/dag/topo.js +30 -0
  168. package/dist/workflows/dag/types.js +275 -0
  169. package/dist/workflows/dag/upstream-artifacts.js +95 -0
  170. package/dist/workflows/dag/validate.js +527 -0
  171. package/dist/workflows/dynamic/artifacts.js +65 -0
  172. package/dist/workflows/dynamic/compile.js +360 -0
  173. package/dist/workflows/dynamic/compileTypes.js +1 -0
  174. package/dist/workflows/dynamic/errors.js +5 -0
  175. package/dist/workflows/dynamic/index.js +7 -0
  176. package/dist/workflows/dynamic/profiles.js +156 -0
  177. package/dist/workflows/dynamic/spec.js +114 -0
  178. package/dist/workflows/dynamic/validate.js +275 -0
  179. package/dist/workflows/loop/actions.js +1334 -0
  180. package/dist/workflows/loop/benchmark.js +510 -0
  181. package/dist/workflows/loop/closeout.js +134 -0
  182. package/dist/workflows/loop/context.js +48 -0
  183. package/dist/workflows/loop/events.js +25 -0
  184. package/dist/workflows/loop/hash.js +32 -0
  185. package/dist/workflows/loop/index.js +8 -0
  186. package/dist/workflows/loop/paths.js +17 -0
  187. package/dist/workflows/loop/rounds.js +81 -0
  188. package/dist/workflows/loop/signals.js +55 -0
  189. package/dist/workflows/loop/state.js +116 -0
  190. package/dist/workflows/loop/templates.js +54 -0
  191. package/dist/workflows/loop/types.js +28 -0
  192. package/docs/README.md +62 -0
  193. package/docs/agent-dag-recovery-playbook.md +158 -0
  194. package/docs/agent-dag-runner.md +40 -0
  195. package/docs/cursor-executor-usage.md +25 -0
  196. package/docs/decisions/README.md +3 -0
  197. package/docs/design/README.md +36 -0
  198. package/docs/development-principles.md +71 -0
  199. package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -0
  200. package/docs/exec-plans/README.md +6 -0
  201. package/docs/exec-plans/active/README.md +5 -0
  202. package/docs/exec-plans/completed/README.md +5 -0
  203. package/docs/feature-workflow.md +184 -0
  204. package/docs/harness-methodology-debugging.md +153 -0
  205. package/docs/harness-methodology-tdd.md +130 -0
  206. package/docs/harness-methodology-verification.md +27 -0
  207. package/docs/loop-agent-harness.md +42 -0
  208. package/docs/progress/README.md +3 -0
  209. package/docs/reports/README.md +3 -0
  210. package/docs/templates/adr.md +60 -0
  211. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
  212. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
  213. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
  214. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
  215. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
  216. package/docs/templates/agent-dag-report.schema.json +423 -0
  217. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
  218. package/docs/templates/agent-dag.base.json +195 -0
  219. package/docs/templates/agent-dag.final-verification.json +190 -0
  220. package/docs/templates/agent-dag.schema.json +316 -0
  221. package/docs/templates/agent-dag.supervised-implementation.json +500 -0
  222. package/docs/templates/exec-plan.md +64 -0
  223. package/docs/templates/feature-spec.md +53 -0
  224. package/docs/templates/hybrid-dag.json +193 -0
  225. package/docs/templates/progress-log.md +17 -0
  226. package/docs/templates/project-start-checklist.md +9 -0
  227. package/docs/templates/qa-report.md +42 -0
  228. package/docs/templates/sprint-contract.md +29 -0
  229. package/docs/verification-matrix.md +30 -0
  230. package/examples/decision-gate-agent-dag.json +123 -0
  231. package/examples/example-dag.json +51 -0
  232. package/examples/hybrid-loop-agent-dag.json +194 -0
  233. package/harness.json +92 -0
  234. package/package.json +61 -0
  235. package/skills/ai-engineering-context/SKILL.md +48 -0
  236. package/skills/loop-agent/SKILL.md +260 -0
  237. package/skills/loop-agent/references/README.md +63 -0
  238. package/skills/loop-agent/references/command-reference.md +315 -0
  239. package/skills/loop-agent/references/harness-policy.md +258 -0
  240. package/skills/loop-agent/references/hybrid-dag.md +216 -0
  241. package/skills/loop-agent/references/learned/README.md +21 -0
  242. package/skills/loop-agent/references/model-routing.md +36 -0
  243. package/skills/loop-agent/references/multi-worktree.md +54 -0
  244. package/skills/loop-agent/references/one-shot-runs.md +85 -0
  245. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -0
  246. package/skills/loop-agent/references/pi-prompt.md +23 -0
  247. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +83 -0
  248. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -0
  249. package/skills/loop-agent/references/task-workflow.md +84 -0
  250. package/skills/loop-agent/references/verification-and-failure-handling.md +74 -0
  251. package/skills/requesting-code-review/SKILL.md +101 -0
  252. package/skills/requesting-code-review/code-reviewer.md +168 -0
  253. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  254. package/skills/systematic-debugging/SKILL.md +296 -0
  255. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  256. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  257. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  258. package/skills/systematic-debugging/find-polluter.sh +63 -0
  259. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  260. package/skills/systematic-debugging/test-academic.md +14 -0
  261. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  262. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  263. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  264. package/skills/verification-before-completion/SKILL.md +154 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Feature-study reference indexing (compatibility-only).
3
+ * Used by `study init` and `reference index`.
4
+ */
5
+ import { access, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ const DEFAULT_MAX_FILES_PER_REPO = 24;
8
+ const DEFAULT_MAX_TOTAL_FILES = 48;
9
+ const DEFAULT_MAX_FILE_BYTES = 512_000;
10
+ export function isFeatureStudyTask(taskConfig) {
11
+ return taskConfig.taskKind === 'feature-study';
12
+ }
13
+ export function getReferenceManifestPath(repoRoot, taskId) {
14
+ return path.join(repoRoot, '.harness', 'tasks', taskId, 'source', 'references', 'index.json');
15
+ }
16
+ export async function readReferenceManifest(repoRoot, taskId) {
17
+ const manifestPath = getReferenceManifestPath(repoRoot, taskId);
18
+ try {
19
+ const raw = await readFile(manifestPath, 'utf-8');
20
+ return JSON.parse(raw);
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ export async function buildReferenceIndex(repoRoot, taskId, taskConfig) {
27
+ const repos = taskConfig.referenceRepos ?? [];
28
+ const docs = taskConfig.referenceDocs ?? [];
29
+ if (repos.length === 0 && docs.length === 0) {
30
+ throw new Error('task.json has no referenceRepos or referenceDocs; add references before running reference index');
31
+ }
32
+ const maxPerRepo = taskConfig.referenceMaxFilesPerRepo ?? DEFAULT_MAX_FILES_PER_REPO;
33
+ const maxTotal = taskConfig.referenceMaxTotalFiles ?? DEFAULT_MAX_TOTAL_FILES;
34
+ let remaining = maxTotal;
35
+ const indexedRepos = [];
36
+ for (const repo of repos) {
37
+ const root = path.resolve(repo.path);
38
+ await assertReadableDirectory(root, `reference repo "${repo.name}"`);
39
+ const focusGlobs = repo.focusGlobs?.length ? repo.focusGlobs : ['**/*'];
40
+ const files = await collectRepoFiles(root, focusGlobs, Math.min(maxPerRepo, remaining));
41
+ remaining -= files.files.length;
42
+ indexedRepos.push({
43
+ name: repo.name,
44
+ root,
45
+ focusGlobs,
46
+ files: files.files,
47
+ truncated: files.truncated,
48
+ });
49
+ if (remaining <= 0)
50
+ break;
51
+ }
52
+ const indexedDocs = [];
53
+ for (const doc of docs) {
54
+ const absolutePath = path.resolve(doc.path);
55
+ await assertReadableFile(absolutePath, `reference doc "${doc.name ?? absolutePath}"`);
56
+ const fileStat = await stat(absolutePath);
57
+ indexedDocs.push({
58
+ name: doc.name ?? path.basename(absolutePath),
59
+ absolutePath,
60
+ sizeBytes: fileStat.size,
61
+ });
62
+ }
63
+ const index = {
64
+ generatedAt: new Date().toISOString(),
65
+ taskKind: taskConfig.taskKind,
66
+ repos: indexedRepos,
67
+ docs: indexedDocs,
68
+ };
69
+ const manifestPath = getReferenceManifestPath(repoRoot, taskId);
70
+ await mkdir(path.dirname(manifestPath), { recursive: true });
71
+ await writeFile(manifestPath, `${JSON.stringify(index, null, 2)}\n`, 'utf-8');
72
+ return index;
73
+ }
74
+ export async function getReferenceContextFiles(repoRoot, taskId, taskConfig) {
75
+ if (!isFeatureStudyTask(taskConfig)) {
76
+ return [];
77
+ }
78
+ const manifest = await readReferenceManifest(repoRoot, taskId);
79
+ if (!manifest) {
80
+ return [];
81
+ }
82
+ const files = [getReferenceManifestPath(repoRoot, taskId)];
83
+ for (const repo of manifest.repos) {
84
+ for (const file of repo.files) {
85
+ files.push(file.absolutePath);
86
+ }
87
+ }
88
+ for (const doc of manifest.docs) {
89
+ files.push(doc.absolutePath);
90
+ }
91
+ return uniqueKeepOrder(files);
92
+ }
93
+ function uniqueKeepOrder(items) {
94
+ const seen = new Set();
95
+ const result = [];
96
+ for (const item of items) {
97
+ if (seen.has(item))
98
+ continue;
99
+ seen.add(item);
100
+ result.push(item);
101
+ }
102
+ return result;
103
+ }
104
+ async function collectRepoFiles(root, focusGlobs, maxFiles) {
105
+ const candidates = await listFilesRecursive(root);
106
+ const matched = candidates
107
+ .map((absolutePath) => ({
108
+ absolutePath,
109
+ relativePath: path.relative(root, absolutePath).replace(/\\/g, '/'),
110
+ }))
111
+ .filter((entry) => focusGlobs.some((pattern) => matchGlob(pattern, entry.relativePath)))
112
+ .sort((a, b) => a.relativePath.localeCompare(b.relativePath));
113
+ const files = [];
114
+ let truncated = false;
115
+ for (const entry of matched) {
116
+ if (files.length >= maxFiles) {
117
+ truncated = true;
118
+ break;
119
+ }
120
+ const fileStat = await stat(entry.absolutePath);
121
+ if (!fileStat.isFile())
122
+ continue;
123
+ if (fileStat.size > DEFAULT_MAX_FILE_BYTES)
124
+ continue;
125
+ const lower = entry.relativePath.toLowerCase();
126
+ if (lower.includes('/target/') || lower.includes('/node_modules/') || lower.includes('/.git/')) {
127
+ continue;
128
+ }
129
+ files.push({
130
+ relativePath: entry.relativePath,
131
+ absolutePath: entry.absolutePath,
132
+ sizeBytes: fileStat.size,
133
+ });
134
+ }
135
+ return { files, truncated };
136
+ }
137
+ async function listFilesRecursive(dir) {
138
+ let entries;
139
+ try {
140
+ entries = await readdir(dir, { withFileTypes: true });
141
+ }
142
+ catch {
143
+ return [];
144
+ }
145
+ const nested = await Promise.all(entries.map(async (entry) => {
146
+ const target = path.join(dir, entry.name);
147
+ if (entry.isDirectory())
148
+ return listFilesRecursive(target);
149
+ return [target];
150
+ }));
151
+ return nested.flat();
152
+ }
153
+ export function matchGlob(pattern, relativePath) {
154
+ const normalizedPattern = pattern.replace(/\\/g, '/');
155
+ const normalizedPath = relativePath.replace(/\\/g, '/');
156
+ const regex = globToRegExp(normalizedPattern);
157
+ return regex.test(normalizedPath);
158
+ }
159
+ function globToRegExp(glob) {
160
+ let regex = '^';
161
+ for (let i = 0; i < glob.length; i += 1) {
162
+ const char = glob[i];
163
+ if (char === '*') {
164
+ const next = glob[i + 1];
165
+ if (next === '*') {
166
+ regex += '.*';
167
+ i += 1;
168
+ if (glob[i + 1] === '/') {
169
+ i += 1;
170
+ }
171
+ }
172
+ else {
173
+ regex += '[^/]*';
174
+ }
175
+ continue;
176
+ }
177
+ if (char === '?') {
178
+ regex += '[^/]';
179
+ continue;
180
+ }
181
+ regex += char.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
182
+ }
183
+ regex += '$';
184
+ return new RegExp(regex);
185
+ }
186
+ async function assertReadableDirectory(target, label) {
187
+ try {
188
+ const fileStat = await stat(target);
189
+ if (!fileStat.isDirectory()) {
190
+ throw new Error(`${label} is not a directory: ${target}`);
191
+ }
192
+ }
193
+ catch (error) {
194
+ if (error instanceof Error && error.message.includes('is not a directory')) {
195
+ throw error;
196
+ }
197
+ throw new Error(`${label} not found or unreadable: ${target}`);
198
+ }
199
+ }
200
+ async function assertReadableFile(target, label) {
201
+ try {
202
+ await access(target);
203
+ const fileStat = await stat(target);
204
+ if (!fileStat.isFile()) {
205
+ throw new Error(`${label} is not a file: ${target}`);
206
+ }
207
+ }
208
+ catch (error) {
209
+ if (error instanceof Error && error.message.includes('is not a file')) {
210
+ throw error;
211
+ }
212
+ throw new Error(`${label} not found or unreadable: ${target}`);
213
+ }
214
+ }
215
+ export function parseReferenceRepoArg(value) {
216
+ const separator = value.indexOf(':');
217
+ if (separator <= 0) {
218
+ throw new Error(`invalid --reference-repo value "${value}"; expected name:/absolute/path`);
219
+ }
220
+ const name = value.slice(0, separator).trim();
221
+ const repoPath = value.slice(separator + 1).trim();
222
+ if (!name || !repoPath) {
223
+ throw new Error(`invalid --reference-repo value "${value}"; expected name:/absolute/path`);
224
+ }
225
+ return { name, path: repoPath, focusGlobs: [] };
226
+ }
227
+ export function parseReferenceDocArg(value) {
228
+ const separator = value.indexOf(':');
229
+ if (separator <= 0) {
230
+ return { name: path.basename(value), path: value };
231
+ }
232
+ const name = value.slice(0, separator).trim();
233
+ const docPath = value.slice(separator + 1).trim();
234
+ if (!name || !docPath) {
235
+ throw new Error(`invalid --reference-doc value "${value}"; expected name:/absolute/path or /absolute/path`);
236
+ }
237
+ return { name, path: docPath };
238
+ }
@@ -0,0 +1,19 @@
1
+ import { DEFAULT_TIMEOUT_MS, MAX_ALLOWED_TIMEOUT_MS } from "../executors/pi-executor.js";
2
+ export { MAX_ALLOWED_TIMEOUT_MS };
3
+ /**
4
+ * Clamp timeout values for Pi steps. Undefined or non-positive values fall back to
5
+ * DEFAULT_TIMEOUT_MS so task-level invalid values do not disable Pi execution timeouts.
6
+ */
7
+ export function clampTimeout(value, fallbackMs = DEFAULT_TIMEOUT_MS) {
8
+ const effective = value !== undefined && value > 0 ? value : fallbackMs;
9
+ return Math.min(effective, MAX_ALLOWED_TIMEOUT_MS);
10
+ }
11
+ /**
12
+ * Clamp only the maximum timeout while preserving 0/negative values.
13
+ *
14
+ * Verify commands historically treat timeoutMs <= 0 as "no timeout" in
15
+ * executeCommand(), so verify must not use clampTimeout()'s fallback semantics.
16
+ */
17
+ export function clampTimeoutMaxOnly(value) {
18
+ return Math.min(value, MAX_ALLOWED_TIMEOUT_MS);
19
+ }
@@ -0,0 +1 @@
1
+ export * from "./timeout-policy.js";
@@ -0,0 +1,5 @@
1
+ /** Compatibility barrel — prefer domain-specific type modules for new definitions. */
2
+ export * from "../governance/manifest-types.js";
3
+ export * from "../task/workflow-state-types.js";
4
+ export * from "../task/config-types.js";
5
+ export * from "../adapters/types.js";
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { taskExecutorSchema } from "../governance/manifest-types.js";
3
+ export const taskFlowSchema = z.enum([
4
+ "auto",
5
+ "micro",
6
+ "standard",
7
+ "spec",
8
+ "loop",
9
+ "feature-study",
10
+ ]);
11
+ export const taskKindSchema = z.enum(["standard", "feature-study"]);
12
+ export const referenceRepoConfigSchema = z.object({
13
+ name: z.string().min(1),
14
+ path: z.string().min(1),
15
+ focusGlobs: z.array(z.string().min(1)).optional().default([]),
16
+ });
17
+ export const referenceDocConfigSchema = z.object({
18
+ name: z.string().min(1).optional(),
19
+ path: z.string().min(1),
20
+ });
21
+ export const taskComplexitySchema = z.enum(["small", "medium", "large"]);
22
+ export const contextProfileSchema = z.enum(["full", "slim"]);
23
+ export const piSubagentModeSchema = z.enum(["off", "analyze-plan", "full"]);
24
+ export const verifyModeSchema = z.enum(["parallel", "serial"]);
25
+ export const verifyPresetSchema = z.enum(["auto", "quick", "standard", "full"]);
26
+ export const verifyQuotaSchema = z.enum(["1", "3", "full"]);
27
+ export const dagVerifyStrategySchema = z
28
+ .object({
29
+ intermediateQuota: verifyQuotaSchema.optional(),
30
+ finalQuota: z.literal("full").optional().default("full"),
31
+ focusedCommandSource: z
32
+ .literal("adapter")
33
+ .optional()
34
+ .default("adapter"),
35
+ })
36
+ .optional();
37
+ export const loopAutoWritePolicySchema = z.enum([
38
+ "off",
39
+ "approval-required",
40
+ "enabled",
41
+ ]);
42
+ export const convergenceConfigSchema = z.object({
43
+ enabled: z.boolean().optional().default(false),
44
+ maxPasses: z.number().int().positive().optional().default(3),
45
+ stopOnVerdictPass: z.boolean().optional().default(true),
46
+ stopOnHardVerifyPass: z.boolean().optional().default(true),
47
+ pauseOnRegression: z.boolean().optional().default(true),
48
+ });
49
+ export const taskConfigSchema = z.object({
50
+ taskId: z.string(),
51
+ title: z.string(),
52
+ sourceFiles: z.array(z.string()),
53
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地 */
54
+ taskKind: taskKindSchema.optional().default("standard"),
55
+ referenceRepos: z.array(referenceRepoConfigSchema).optional().default([]),
56
+ referenceDocs: z.array(referenceDocConfigSchema).optional().default([]),
57
+ referenceMaxFilesPerRepo: z.number().int().positive().optional(),
58
+ referenceMaxTotalFiles: z.number().int().positive().optional(),
59
+ allowedPaths: z.array(z.string()).optional().default([]),
60
+ forbiddenPaths: z.array(z.string()).optional().default([]),
61
+ hardConstraints: z.array(z.string()).optional().default([]),
62
+ autoCommitAfterVerify: z.boolean().optional().default(true),
63
+ autoCommitMessage: z.string().optional().default(""),
64
+ /** Explicit reason that a medium/large task could not use DAG before loop cursor-fix. */
65
+ dagFallbackReason: z.string().optional(),
66
+ /** full: attach governance bundle; slim: analyze/plan 只带最少仓库上下文(source + harness 等) */
67
+ contextProfile: contextProfileSchema.optional().default("full"),
68
+ timeoutMs: z.number().int().positive().optional(),
69
+ flow: taskFlowSchema.optional().default("auto"),
70
+ complexity: taskComplexitySchema.optional().default("medium"),
71
+ verifyMode: verifyModeSchema.optional().default("parallel"),
72
+ verifyPreset: verifyPresetSchema.optional().default("auto"),
73
+ /** Verification selection policy. Intermediate loops may use quota; final gates still run full required verification. */
74
+ verifyQuota: verifyQuotaSchema.optional().default("full"),
75
+ /** DAG supervised verify strategy. Final quota is fixed to full; intermediate quota may reduce cost. */
76
+ dagVerifyStrategy: dagVerifyStrategySchema,
77
+ verifyRetryCount: z.number().int().min(0).optional().default(0),
78
+ verifyFailFast: z.boolean().optional().default(false),
79
+ contextMaxFiles: z.number().int().min(0).optional().default(0),
80
+ maxFixLoops: z.number().int().min(0).optional().default(2),
81
+ /** Supervised DAG convergence is opt-in until runtime smoke evidence is stronger. */
82
+ convergence: convergenceConfigSchema.optional().default({ enabled: false }),
83
+ /** Outer loop auto mode never writes by default; cursor-fix requires this policy plus bounded path guards. */
84
+ loopAutoWritePolicy: loopAutoWritePolicySchema.optional().default("off"),
85
+ requireRetrospective: z.boolean().optional().default(false),
86
+ /** inherit: use host shell env; clean: sanitized env for deterministic verify */
87
+ verifyEnv: z.enum(["inherit", "clean"]).optional().default("clean"),
88
+ /** Max legacy goal continuation attempts before auto-pausing an active goal */
89
+ maxGoalContinuationsPerRun: z.number().int().positive().optional().default(5),
90
+ /** Pi subagent assisted mode: 'off' (default), 'analyze-plan', or 'full' */
91
+ piSubagentMode: piSubagentModeSchema.optional().default("off"),
92
+ /** Leaf executor: default pi; cursor requires delegate + worktree isolation */
93
+ executor: taskExecutorSchema.optional().default("pi"),
94
+ /** Cursor model override (e.g. gpt-5.5); falls back to harness executors.cursor.defaultModel */
95
+ cursorModel: z.string().optional(),
96
+ notes: z.string().optional().default(""),
97
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./config-types.js";
2
+ export * from "./workflow-state-types.js";
@@ -0,0 +1,220 @@
1
+ import { access, mkdir, readFile, symlink, unlink, writeFile } from 'node:fs/promises';
2
+ import { spawn } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { initializeArtifacts } from '../shared/artifacts-core.js';
5
+ import { createWorktree } from './worktree.js';
6
+ import { loadHarnessManifest } from '../governance/harness.js';
7
+ import { getTaskPaths, loadTaskConfig } from './runtime.js';
8
+ import { copyDir } from '../records/harvest.js';
9
+ import { saveWorkflowState } from './state.js';
10
+ import { workflowStateSchema } from '../shared/types.js';
11
+ import { assertCursorExecutorAvailable, resolveTaskExecutor, validateCursorTaskPreflight } from '../executors/config-core.js';
12
+ import { resolveDelegateExecutionMode } from '../executors/cursor-execution-log.js';
13
+ /**
14
+ * Atomic delegation: validate source → check conflicts → create worktree →
15
+ * sync source → optionally symlink node_modules.
16
+ *
17
+ * Execution runs in-process for Cursor SDK; Pi worktrees are prepared for manual DAG follow-up.
18
+ */
19
+ export async function delegateTask(repoRoot, manifest, taskId, opts = {}) {
20
+ const symlinkEnabled = opts.symlinkNodeModules !== false;
21
+ await validateSourceMaterials(repoRoot, taskId);
22
+ const taskConfig = await loadTaskConfig(repoRoot, taskId);
23
+ const effectiveManifest = manifest ?? (await loadHarnessManifest(repoRoot));
24
+ const executor = resolveTaskExecutor(taskConfig, opts.executor);
25
+ if (executor === 'cursor') {
26
+ assertCursorExecutorAvailable(effectiveManifest);
27
+ validateCursorTaskPreflight(taskConfig);
28
+ }
29
+ await validateNoConflicts(repoRoot, taskId, opts.branch);
30
+ const worktree = await createWorktree(repoRoot, effectiveManifest, {
31
+ taskId,
32
+ branch: opts.branch,
33
+ base: opts.base,
34
+ });
35
+ await syncSourceToWorktree(repoRoot, taskId, worktree.path, executor);
36
+ let symlinkedNodeModules = false;
37
+ if (symlinkEnabled) {
38
+ symlinkedNodeModules = await setupNodeModulesSymlink(repoRoot, worktree.path);
39
+ }
40
+ const sessionName = `task-${taskId}`;
41
+ return {
42
+ taskId,
43
+ sessionName,
44
+ worktreePath: worktree.path,
45
+ branch: worktree.branch,
46
+ baseBranch: worktree.base,
47
+ symlinkedNodeModules,
48
+ executor,
49
+ executionMode: resolveDelegateExecutionMode(executor),
50
+ };
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Internal helpers
54
+ // ---------------------------------------------------------------------------
55
+ async function validateSourceMaterials(repoRoot, taskId) {
56
+ const { taskConfigPath, sourceDir } = getTaskPaths(repoRoot, taskId);
57
+ try {
58
+ await access(taskConfigPath);
59
+ }
60
+ catch (err) {
61
+ if (err.code === 'ENOENT') {
62
+ throw new Error(`source validation failed: task.json not found at ${taskConfigPath}`);
63
+ }
64
+ throw err;
65
+ }
66
+ try {
67
+ await loadTaskConfig(repoRoot, taskId);
68
+ }
69
+ catch (err) {
70
+ const msg = err instanceof Error ? err.message : String(err);
71
+ throw new Error(`source validation failed: task.json is invalid at ${taskConfigPath}: ${msg}`);
72
+ }
73
+ await assertNonEmptyFile(path.join(sourceDir, '需求.md'), 'source validation failed: 需求.md');
74
+ await assertNonEmptyFile(path.join(sourceDir, '执行约束.md'), 'source validation failed: 执行约束.md');
75
+ }
76
+ async function assertNonEmptyFile(filePath, errPrefix) {
77
+ let content;
78
+ try {
79
+ content = await readFile(filePath, 'utf-8');
80
+ }
81
+ catch (err) {
82
+ if (err.code === 'ENOENT') {
83
+ throw new Error(`${errPrefix}: file not found at ${filePath}`);
84
+ }
85
+ throw err;
86
+ }
87
+ if (content.trim().length === 0) {
88
+ throw new Error(`${errPrefix}: file is empty at ${filePath}`);
89
+ }
90
+ }
91
+ async function validateNoConflicts(repoRoot, taskId, branch) {
92
+ const targetBranch = branch ?? `task/${taskId}`;
93
+ const existing = await tryRunGit(repoRoot, ['branch', '--list', targetBranch]);
94
+ if (existing && existing.trim().length > 0) {
95
+ console.error(`[delegate] warning: branch "${targetBranch}" already exists; will reuse it for the worktree.`);
96
+ }
97
+ }
98
+ async function syncSourceToWorktree(repoRoot, taskId, worktreePath, executor) {
99
+ const srcPaths = getTaskPaths(repoRoot, taskId);
100
+ const destPaths = getTaskPaths(worktreePath, taskId);
101
+ const destTaskDir = destPaths.taskDir;
102
+ const destSourceDir = path.join(destTaskDir, 'source');
103
+ const destTaskConfigPath = path.join(destTaskDir, 'task.json');
104
+ await mkdir(destSourceDir, { recursive: true });
105
+ await copyDir(srcPaths.sourceDir, destSourceDir);
106
+ const rawJson = await readFile(srcPaths.taskConfigPath, 'utf-8');
107
+ const parsedTaskConfig = JSON.parse(rawJson);
108
+ parsedTaskConfig.executor = executor;
109
+ await mkdir(path.dirname(destTaskConfigPath), { recursive: true });
110
+ await writeFile(destTaskConfigPath, `${JSON.stringify(parsedTaskConfig, null, 2)}\n`, 'utf-8');
111
+ await mkdir(destPaths.logsDir, { recursive: true });
112
+ await initializeArtifacts(destTaskDir);
113
+ await writeFile(path.join(destPaths.logsDir, 'executor.jsonl'), '', 'utf-8');
114
+ await writeFile(path.join(destPaths.logsDir, 'decisions.jsonl'), '', 'utf-8');
115
+ const state = await loadOrCreateWorkflowState(srcPaths.statePath, taskId, executor);
116
+ await saveWorkflowState(destPaths.statePath, state);
117
+ }
118
+ async function loadOrCreateWorkflowState(statePath, taskId, executor) {
119
+ try {
120
+ const raw = await readFile(statePath, 'utf-8');
121
+ return workflowStateSchema.parse(JSON.parse(raw));
122
+ }
123
+ catch {
124
+ const now = new Date().toISOString();
125
+ return {
126
+ taskId,
127
+ status: 'draft',
128
+ currentStep: 'analyze',
129
+ completedSteps: [],
130
+ lastUpdated: now,
131
+ executor: {
132
+ name: executor,
133
+ mode: executor === 'cursor' ? 'sdk' : 'json',
134
+ },
135
+ artifacts: {
136
+ analysis: false,
137
+ plan: false,
138
+ modifyLog: false,
139
+ verifyResult: false,
140
+ retrospective: false,
141
+ },
142
+ sourceState: {
143
+ currentHash: '',
144
+ plannedHash: '',
145
+ trackedFiles: [],
146
+ lastCheckedAt: '',
147
+ lastPlannedAt: '',
148
+ lastPlannedStep: null,
149
+ stalePlan: false,
150
+ },
151
+ goal: {
152
+ objective: '',
153
+ status: 'paused',
154
+ tokenBudget: null,
155
+ tokensUsed: 0,
156
+ timeUsedSeconds: 0,
157
+ pendingObjectiveUpdatedNotice: false,
158
+ pendingBudgetLimitNotice: false,
159
+ continuationRuns: 0,
160
+ createdAt: '',
161
+ updatedAt: now,
162
+ },
163
+ decisionLog: [],
164
+ };
165
+ }
166
+ }
167
+ async function setupNodeModulesSymlink(repoRoot, worktreePath) {
168
+ let linkedAny = false;
169
+ const hostNodeModules = path.join(repoRoot, 'node_modules');
170
+ const worktreeNodeModules = path.join(worktreePath, 'node_modules');
171
+ const linkedLoopAgent = await linkIfExists(hostNodeModules, worktreeNodeModules, `[delegate] warning: host node_modules not found at ${hostNodeModules}; worktree may need npm install.`);
172
+ linkedAny = linkedAny || linkedLoopAgent;
173
+ return linkedAny;
174
+ }
175
+ async function linkIfExists(hostPath, worktreePath, missingWarning) {
176
+ try {
177
+ await access(hostPath);
178
+ }
179
+ catch {
180
+ console.error(missingWarning);
181
+ return false;
182
+ }
183
+ try {
184
+ await unlink(worktreePath);
185
+ }
186
+ catch (err) {
187
+ const code = err.code;
188
+ if (code !== 'ENOENT') {
189
+ try {
190
+ await access(worktreePath);
191
+ return true;
192
+ }
193
+ catch {
194
+ // fall through and attempt to create symlink
195
+ }
196
+ }
197
+ }
198
+ await mkdir(path.dirname(worktreePath), { recursive: true });
199
+ const relativeTarget = path.relative(path.dirname(worktreePath), hostPath);
200
+ await symlink(relativeTarget, worktreePath, process.platform === 'win32' ? 'junction' : 'dir');
201
+ return true;
202
+ }
203
+ async function tryRunGit(repoRoot, args) {
204
+ return new Promise((resolve) => {
205
+ const child = spawn('git', args, {
206
+ cwd: repoRoot,
207
+ stdio: ['ignore', 'pipe', 'pipe'],
208
+ });
209
+ const out = [];
210
+ child.stdout?.on('data', (chunk) => out.push(chunk));
211
+ child.on('error', () => resolve(null));
212
+ child.on('close', (code) => {
213
+ if (code !== 0) {
214
+ resolve(null);
215
+ return;
216
+ }
217
+ resolve(Buffer.concat(out).toString('utf-8'));
218
+ });
219
+ });
220
+ }
@@ -0,0 +1,51 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { extractRequirementsFromMarkdown, requirementCoverageFailureReasons, runRequirementCoverageAudit, } from '../governance/requirement-coverage.js';
3
+ import { getTaskPaths, getTaskStatus } from './runtime.js';
4
+ const GOAL_SCOPE_START = /<!--\s*goal-scope\s*-->/i;
5
+ const GOAL_SCOPE_END = /<!--\s*\/goal-scope\s*-->/i;
6
+ export function extractGoalAuditScope(text) {
7
+ const start = text.search(GOAL_SCOPE_START);
8
+ const end = text.search(GOAL_SCOPE_END);
9
+ if (start >= 0 && end > start) {
10
+ const scoped = text.slice(start, end);
11
+ return scoped.replace(GOAL_SCOPE_START, '').trim();
12
+ }
13
+ return text;
14
+ }
15
+ export function findUncheckedChecklistItems(text) {
16
+ return /-\s*\[\s\]/.test(text);
17
+ }
18
+ export async function runGoalCompletionAudit(repoRoot, taskId) {
19
+ const state = await getTaskStatus(repoRoot, taskId);
20
+ const reasons = [];
21
+ if (!state.artifacts.verifyResult) {
22
+ reasons.push('missing verify artifact');
23
+ }
24
+ if (!state.completedSteps.includes('verify')) {
25
+ reasons.push('verify step is not marked completed');
26
+ }
27
+ const requirementIssues = await scanUncheckedRequirementItems(repoRoot, taskId);
28
+ reasons.push(...requirementIssues);
29
+ const coverageAudit = await runRequirementCoverageAudit(repoRoot, taskId);
30
+ reasons.push(...requirementCoverageFailureReasons(coverageAudit));
31
+ return {
32
+ ok: reasons.length === 0,
33
+ reasons,
34
+ };
35
+ }
36
+ async function scanUncheckedRequirementItems(repoRoot, taskId) {
37
+ const requirementPath = `${getTaskPaths(repoRoot, taskId).sourceDir}/需求.md`;
38
+ let text = '';
39
+ try {
40
+ text = await readFile(requirementPath, 'utf-8');
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ const { requirements } = extractRequirementsFromMarkdown(text);
46
+ const uncheckedInScope = requirements.filter((item) => item.source === 'checklist' && item.checked === false);
47
+ if (uncheckedInScope.length === 0) {
48
+ return [];
49
+ }
50
+ return uncheckedInScope.map((item) => `source/需求.md has unchecked in-scope checklist item: ${item.label}`);
51
+ }
@@ -0,0 +1,8 @@
1
+ export function isGoalsFeatureEnabled(manifest) {
2
+ return manifest.features?.goals !== false;
3
+ }
4
+ export function assertGoalsFeatureEnabled(manifest) {
5
+ if (!isGoalsFeatureEnabled(manifest)) {
6
+ throw new Error('goals feature is disabled in harness.json (features.goals=false)');
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ export { clearTaskGoal, consumeTaskGoalNotices, getTaskGoal, incrementGoalContinuationRuns, markTaskGoalComplete, recordTaskGoalProgress, setTaskGoal, updateTaskGoalStatus, } from "./runtime.js";
2
+ export * from "./goal-audit.js";
3
+ export * from "./goal-policy.js";
@@ -0,0 +1 @@
1
+ export { CANONICAL_TASK_ID_PATTERN, formatLocalBusinessDate, formatLocalCompactDate, validateNewTaskId, } from "./runtime.js";
@@ -0,0 +1,9 @@
1
+ export * from "./ids.js";
2
+ export * from "./paths.js";
3
+ export * from "./source-state.js";
4
+ export * from "./lifecycle.js";
5
+ export * from "./goal.js";
6
+ export * from "./subagent-guidance.js";
7
+ export * from "./runtime.js";
8
+ export * from "./config.js";
9
+ export * from "./worktree.js";
@@ -0,0 +1 @@
1
+ export { createTask, getNextStep, getSupportedNextCommand, getTaskStatus, markStepCompleted, markTaskCompleted, markTaskFailed, markTaskRunning, markVerifyGoalPending, resolveTaskFlow, shouldRunRetrospective, } from "./runtime.js";
@@ -0,0 +1 @@
1
+ export { getTaskDir, getTaskPaths, listTaskSourceFiles, } from "./runtime.js";