@tea-agent/loop-agent 0.3.0 → 0.5.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 (57) hide show
  1. package/AGENTS.md +16 -14
  2. package/CHANGELOG.md +70 -53
  3. package/README.md +28 -25
  4. package/bin/agent-worker.js +22 -0
  5. package/dist/application/dag/validate-dag.js +14 -1
  6. package/dist/commands/init.js +220 -32
  7. package/dist/executors/config-core.js +3 -2
  8. package/dist/executors/dag-pi-executor.js +8 -1
  9. package/dist/executors/model-routing.js +43 -0
  10. package/dist/governance/manifest-types.js +9 -1
  11. package/dist/worker/cli.js +119 -0
  12. package/dist/worker/loop-agent/command-result.js +1 -0
  13. package/dist/worker/loop-agent/loop-agent-client.js +105 -0
  14. package/dist/worker/loop-agent/parse-json.js +14 -0
  15. package/dist/worker/materialize/harness-task-materializer.js +157 -0
  16. package/dist/worker/pool/failure-routing.js +98 -0
  17. package/dist/worker/pool/run-store.js +125 -0
  18. package/dist/worker/pool/types.js +1 -0
  19. package/dist/worker/preflight.js +108 -0
  20. package/dist/worker/profile-mapping.js +76 -0
  21. package/dist/worker/progress-reporter.js +81 -0
  22. package/dist/worker/report/morning-report.js +69 -0
  23. package/dist/worker/repos/repo-resolver.js +23 -0
  24. package/dist/worker/run-task/run-task.js +359 -0
  25. package/dist/worker/runner/run-ready.js +216 -0
  26. package/dist/worker/task-graph/acceptance-schema.js +25 -0
  27. package/dist/worker/task-graph/ready-queue.js +23 -0
  28. package/dist/worker/task-graph/task-graph-schema.js +28 -0
  29. package/dist/worker/task-graph/types.js +1 -0
  30. package/dist/worker/task-graph/validate.js +188 -0
  31. package/dist/worker/task-spec/complexity-mapping.js +8 -0
  32. package/dist/worker/task-spec/schema.js +116 -0
  33. package/dist/worker/task-spec/types.js +1 -0
  34. package/dist/worker/task-spec/validate.js +352 -0
  35. package/dist/workflows/dag/init-hybrid.js +4 -13
  36. package/dist/workflows/dag/skill-instructions.js +4 -0
  37. package/dist/workflows/dag/types.js +1 -1
  38. package/dist/workflows/dag/validate.js +3 -2
  39. package/docs/README.md +11 -7
  40. package/docs/development-principles.md +2 -0
  41. package/docs/exec-plans/active/README.md +1 -1
  42. package/docs/exec-plans/completed/README.md +8 -0
  43. package/docs/init-surface.manifest.json +199 -175
  44. package/docs/skills/vetted-skill-registry.md +4 -4
  45. package/docs/templates/agent-dag.base.json +1 -1
  46. package/docs/templates/agent-dag.final-verification.json +1 -1
  47. package/docs/templates/agent-dag.supervised-implementation.json +1 -1
  48. package/docs/templates/hybrid-dag.json +1 -1
  49. package/docs/templates/init-evolution-review.md +33 -33
  50. package/examples/example-dag.json +1 -1
  51. package/examples/hybrid-loop-agent-dag.json +1 -1
  52. package/harness.json +7 -32
  53. package/package.json +14 -12
  54. package/skills/init-capability-evolution/SKILL.md +69 -69
  55. package/skills/loop-agent/SKILL.md +2 -0
  56. package/skills/loop-agent/references/command-reference.md +63 -35
  57. package/skills/loop-agent/references/harness-policy.md +2 -1
@@ -0,0 +1,352 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { resolveLoopAgentProfile } from "../profile-mapping.js";
5
+ import { mapRiskLevelToComplexity } from "./complexity-mapping.js";
6
+ import { taskSpecSchema } from "./schema.js";
7
+ const CODE_WRITING_TYPES = new Set([
8
+ "backend-feature",
9
+ "frontend-feature",
10
+ "bugfix",
11
+ "qa-testcode",
12
+ ]);
13
+ const VERIFY_OPTIONAL_TYPES = new Set(["qa-analysis", "reviewer-gate"]);
14
+ const QA_TYPES = new Set([
15
+ "qa-analysis",
16
+ "qa-casegen",
17
+ "qa-testcode",
18
+ "qa-execute",
19
+ "qa-failure-analysis",
20
+ ]);
21
+ export async function validateTaskSpec(input, options = {}) {
22
+ const schemaResult = taskSpecSchema.safeParse(input);
23
+ const fallback = fallbackIdentity(input);
24
+ if (!schemaResult.success) {
25
+ const errors = schemaResult.error.issues.map((issue) => ({
26
+ layer: "schema",
27
+ code: "schema-invalid",
28
+ message: issue.message,
29
+ path: issue.path.join("."),
30
+ }));
31
+ const failure = firstFailure(errors, "schema-invalid", "TaskSpec schema validation failed");
32
+ return buildResult({
33
+ taskId: fallback.taskId,
34
+ featureId: fallback.featureId,
35
+ businessProfile: fallback.businessProfile,
36
+ riskLevel: fallback.riskLevel,
37
+ checks: {
38
+ schema: failure,
39
+ sourceDocs: failureFor("schema-invalid", "Skipped because schema validation failed"),
40
+ acceptanceRefs: failureFor("schema-invalid", "Skipped because schema validation failed"),
41
+ dependsOn: "skipped",
42
+ pathBoundary: failureFor("schema-invalid", "Skipped because schema validation failed"),
43
+ verifyCommands: failureFor("schema-invalid", "Skipped because schema validation failed"),
44
+ profileMapping: failureFor("schema-invalid", "Skipped because schema validation failed"),
45
+ },
46
+ errors,
47
+ warnings: [],
48
+ });
49
+ }
50
+ const taskSpec = schemaResult.data;
51
+ const errors = [];
52
+ const warnings = [];
53
+ const sourceDocsCheck = await checkSourceDocs(taskSpec, options, errors);
54
+ const acceptanceRefsCheck = await checkAcceptanceRefs(taskSpec, options, errors);
55
+ const dependsOnCheck = await checkDependsOn(taskSpec, options, errors);
56
+ const pathBoundaryCheck = checkPathBoundaries(taskSpec, errors, warnings);
57
+ const verifyCommandsCheck = checkVerifyCommands(taskSpec, errors);
58
+ const profileMapping = resolveLoopAgentProfile(taskSpec);
59
+ return buildResult({
60
+ taskId: taskSpec.id,
61
+ featureId: taskSpec.feature_id,
62
+ businessProfile: taskSpec.type,
63
+ riskLevel: taskSpec.risk_level,
64
+ loopAgentProfilePreview: profileMapping.loopAgentProfile,
65
+ checks: {
66
+ schema: "ok",
67
+ sourceDocs: sourceDocsCheck,
68
+ acceptanceRefs: acceptanceRefsCheck,
69
+ dependsOn: dependsOnCheck,
70
+ pathBoundary: pathBoundaryCheck,
71
+ verifyCommands: verifyCommandsCheck,
72
+ profileMapping: "ok",
73
+ },
74
+ errors,
75
+ warnings,
76
+ });
77
+ }
78
+ function buildResult(input) {
79
+ const riskLevel = input.riskLevel === "low" ||
80
+ input.riskLevel === "medium" ||
81
+ input.riskLevel === "high"
82
+ ? input.riskLevel
83
+ : "medium";
84
+ return {
85
+ schemaVersion: 1,
86
+ ok: input.errors.length === 0 && checksAreOk(input.checks),
87
+ taskId: input.taskId,
88
+ featureId: input.featureId,
89
+ businessProfile: input.businessProfile,
90
+ riskLevel: input.riskLevel,
91
+ complexityPreview: mapRiskLevelToComplexity(riskLevel),
92
+ loopAgentProfilePreview: input.loopAgentProfilePreview,
93
+ checks: input.checks,
94
+ errors: input.errors,
95
+ warnings: input.warnings,
96
+ };
97
+ }
98
+ function checksAreOk(checks) {
99
+ return Object.values(checks).every((check) => check === "ok" || check === "skipped");
100
+ }
101
+ async function checkSourceDocs(taskSpec, options, errors) {
102
+ const baseDir = getTaskSpecDir(options);
103
+ if (!baseDir)
104
+ return "ok";
105
+ for (const [key, relativePath] of Object.entries(taskSpec.source_docs)) {
106
+ if (!relativePath)
107
+ continue;
108
+ if (!(await exists(path.resolve(baseDir, relativePath)))) {
109
+ const error = {
110
+ layer: "file-existence",
111
+ code: "source-doc-missing",
112
+ message: `source_docs.${key} does not exist: ${relativePath}`,
113
+ path: `source_docs.${key}`,
114
+ };
115
+ errors.push(error);
116
+ return toFailure(error);
117
+ }
118
+ }
119
+ return "ok";
120
+ }
121
+ async function checkAcceptanceRefs(taskSpec, options, errors) {
122
+ const baseDir = getTaskSpecDir(options);
123
+ if (!baseDir)
124
+ return "ok";
125
+ const acceptancePath = path.resolve(baseDir, taskSpec.source_docs.acceptance);
126
+ if (!(await exists(acceptancePath))) {
127
+ const error = {
128
+ layer: "file-existence",
129
+ code: "acceptance-source-missing",
130
+ message: `acceptance source does not exist: ${taskSpec.source_docs.acceptance}`,
131
+ path: "source_docs.acceptance",
132
+ };
133
+ errors.push(error);
134
+ return toFailure(error);
135
+ }
136
+ const parsed = YAML.parse(await readFile(acceptancePath, "utf-8"));
137
+ const ids = collectStringIds(parsed);
138
+ for (const acceptanceRef of taskSpec.acceptance_refs) {
139
+ if (!ids.has(acceptanceRef)) {
140
+ const error = {
141
+ layer: "file-existence",
142
+ code: "acceptance-ref-missing",
143
+ message: `acceptance ref not found in acceptance source: ${acceptanceRef}`,
144
+ path: "acceptance_refs",
145
+ };
146
+ errors.push(error);
147
+ return toFailure(error);
148
+ }
149
+ }
150
+ return "ok";
151
+ }
152
+ async function checkDependsOn(taskSpec, options, errors) {
153
+ if (taskSpec.depends_on.length === 0)
154
+ return "skipped";
155
+ const baseDir = getTaskSpecDir(options);
156
+ if (!baseDir)
157
+ return "ok";
158
+ for (const dependency of taskSpec.depends_on) {
159
+ const dependencyPath = path.resolve(baseDir, `${dependency}.yaml`);
160
+ if (!(await exists(dependencyPath))) {
161
+ const error = {
162
+ layer: "file-existence",
163
+ code: "depends-on-missing",
164
+ message: `depends_on task file does not exist: ${dependency}.yaml`,
165
+ path: "depends_on",
166
+ };
167
+ errors.push(error);
168
+ return toFailure(error);
169
+ }
170
+ }
171
+ return "ok";
172
+ }
173
+ function checkPathBoundaries(taskSpec, errors, warnings) {
174
+ const start = errors.length;
175
+ for (const [index, allowedPath] of taskSpec.constraints.allowed_paths.entries()) {
176
+ if (isBroadAllowedPath(allowedPath)) {
177
+ errors.push({
178
+ layer: "path-boundary",
179
+ code: "broad-allowed-paths",
180
+ message: `allowed_paths entry is too broad: ${allowedPath}`,
181
+ path: `constraints.allowed_paths[${index}]`,
182
+ });
183
+ }
184
+ for (const [forbiddenIndex, forbiddenPath] of taskSpec.constraints.forbidden_paths.entries()) {
185
+ if (patternsOverlap(allowedPath, forbiddenPath)) {
186
+ errors.push({
187
+ layer: "path-boundary",
188
+ code: "allowed-forbidden-overlap",
189
+ message: `allowed path overlaps forbidden path: ${allowedPath} / ${forbiddenPath}`,
190
+ path: `constraints.forbidden_paths[${forbiddenIndex}]`,
191
+ });
192
+ }
193
+ }
194
+ }
195
+ if (CODE_WRITING_TYPES.has(taskSpec.type) &&
196
+ taskSpec.constraints.allowed_paths.length === 0) {
197
+ errors.push({
198
+ layer: "path-boundary",
199
+ code: "code-task-empty-allowed-paths",
200
+ message: `${taskSpec.type} requires non-empty allowed_paths`,
201
+ path: "constraints.allowed_paths",
202
+ });
203
+ }
204
+ if (taskSpec.constraints.forbidden_paths.length === 0) {
205
+ errors.push({
206
+ layer: "path-boundary",
207
+ code: "forbidden-paths-empty",
208
+ message: "TaskSpec requires non-empty forbidden_paths",
209
+ path: "constraints.forbidden_paths",
210
+ });
211
+ }
212
+ if (QA_TYPES.has(taskSpec.type) && !hasQaBusinessCodeForbiddenPath(taskSpec)) {
213
+ errors.push({
214
+ layer: "path-boundary",
215
+ code: "qa-business-code-not-forbidden",
216
+ message: "QA tasks must forbid common business code directories",
217
+ path: "constraints.forbidden_paths",
218
+ });
219
+ }
220
+ const complexity = mapRiskLevelToComplexity(taskSpec.risk_level);
221
+ if ((complexity === "medium" || complexity === "large") &&
222
+ taskSpec.constraints.allowed_paths.length === 0) {
223
+ errors.push({
224
+ layer: "path-boundary",
225
+ code: "empty-allowed-paths-for-complexity",
226
+ message: `${complexity} tasks require non-empty allowed_paths before materialize`,
227
+ path: "constraints.allowed_paths",
228
+ });
229
+ }
230
+ if (complexity === "small" && taskSpec.constraints.allowed_paths.length === 0) {
231
+ warnings.push({
232
+ code: "empty-allowed-paths-path-guard-risk",
233
+ message: "empty allowed_paths will make non-harness writes fail path guard",
234
+ path: "constraints.allowed_paths",
235
+ });
236
+ }
237
+ const pathErrors = errors.slice(start).filter((error) => error.layer === "path-boundary");
238
+ if (pathErrors.length === 0)
239
+ return "ok";
240
+ return toFailure(pathErrors[0]);
241
+ }
242
+ function checkVerifyCommands(taskSpec, errors) {
243
+ if (!VERIFY_OPTIONAL_TYPES.has(taskSpec.type) &&
244
+ taskSpec.verify.commands.length === 0) {
245
+ const error = {
246
+ layer: "file-existence",
247
+ code: "verify-commands-empty",
248
+ message: `${taskSpec.type} requires verify.commands`,
249
+ path: "verify.commands",
250
+ };
251
+ errors.push(error);
252
+ return toFailure(error);
253
+ }
254
+ return "ok";
255
+ }
256
+ function getTaskSpecDir(options) {
257
+ return options.taskSpecPath ? path.dirname(options.taskSpecPath) : undefined;
258
+ }
259
+ async function exists(filePath) {
260
+ try {
261
+ await access(filePath);
262
+ return true;
263
+ }
264
+ catch {
265
+ return false;
266
+ }
267
+ }
268
+ function collectStringIds(value, ids = new Set()) {
269
+ if (Array.isArray(value)) {
270
+ for (const item of value)
271
+ collectStringIds(item, ids);
272
+ return ids;
273
+ }
274
+ if (value && typeof value === "object") {
275
+ for (const [key, child] of Object.entries(value)) {
276
+ if (key === "id" && typeof child === "string")
277
+ ids.add(child);
278
+ collectStringIds(child, ids);
279
+ }
280
+ }
281
+ return ids;
282
+ }
283
+ function isBroadAllowedPath(pattern) {
284
+ const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
285
+ return normalized === "**" || normalized === "./**" || normalized === ".";
286
+ }
287
+ function probeForPattern(pattern) {
288
+ return pattern
289
+ .replace(/\\/g, "/")
290
+ .replace(/\/\*\*$/, "/_probe_")
291
+ .replace(/\*\*$/, "_probe_")
292
+ .replace(/\*/g, "_")
293
+ .replace(/^\.\//, "");
294
+ }
295
+ function patternsOverlap(left, right) {
296
+ const leftProbe = probeForPattern(left);
297
+ const rightProbe = probeForPattern(right);
298
+ return (pathMatchesPattern(leftProbe, right) ||
299
+ pathMatchesPattern(rightProbe, left) ||
300
+ leftProbe === rightProbe);
301
+ }
302
+ function pathMatchesPattern(filePath, pattern) {
303
+ const normalizedPath = filePath.replace(/\\/g, "/");
304
+ const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
305
+ if (normalizedPattern.endsWith("/**")) {
306
+ const prefix = normalizedPattern.slice(0, -3);
307
+ return normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`);
308
+ }
309
+ if (normalizedPattern.includes("*")) {
310
+ const escaped = normalizedPattern
311
+ .split("*")
312
+ .map((part) => part.replace(/[|\\{}()[\]^$+?.]/g, "\\$&"))
313
+ .join(".*");
314
+ return new RegExp(`^${escaped}$`).test(normalizedPath);
315
+ }
316
+ return normalizedPath === normalizedPattern;
317
+ }
318
+ function hasQaBusinessCodeForbiddenPath(taskSpec) {
319
+ return taskSpec.constraints.forbidden_paths.some((forbiddenPath) => {
320
+ const normalized = forbiddenPath.replace(/\\/g, "/");
321
+ return (normalized.includes("/src/") ||
322
+ normalized.includes("src/**") ||
323
+ normalized.includes("/services/") ||
324
+ normalized.includes("services/**") ||
325
+ normalized.includes("/app/") ||
326
+ normalized.includes("app/**") ||
327
+ normalized.includes("/apps/"));
328
+ });
329
+ }
330
+ function fallbackIdentity(input) {
331
+ const record = input && typeof input === "object" ? input : {};
332
+ return {
333
+ taskId: typeof record.id === "string" ? record.id : "",
334
+ featureId: typeof record.feature_id === "string" ? record.feature_id : "",
335
+ businessProfile: typeof record.type === "string" ? record.type : "",
336
+ riskLevel: typeof record.risk_level === "string" ? record.risk_level : "medium",
337
+ };
338
+ }
339
+ function failureFor(code, message, pathValue) {
340
+ return { status: "failed", code, message, path: pathValue };
341
+ }
342
+ function firstFailure(errors, code, message) {
343
+ return errors[0] ? toFailure(errors[0]) : failureFor(code, message);
344
+ }
345
+ function toFailure(error) {
346
+ return {
347
+ status: "failed",
348
+ code: error.code,
349
+ message: error.message,
350
+ path: error.path,
351
+ };
352
+ }
@@ -2,7 +2,7 @@ import { access, readFile, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { assertValidDagSpec } from "./validate.js";
5
- import { parseDagSpec, } from "./types.js";
5
+ import { DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
6
6
  import { pathMatchesPattern } from "../../shared/git-progress.js";
7
7
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
8
8
  import { resolveAdapter } from "../../adapters/index.js";
@@ -10,6 +10,7 @@ import { loadHarnessManifest } from "../../governance/harness.js";
10
10
  import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
11
11
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
12
12
  import { resolveVerifyPreset } from "../../executors/shell-verification.js";
13
+ import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
13
14
  const REQUIREMENT_FILE = "需求.md";
14
15
  const CONSTRAINT_FILE = "执行约束.md";
15
16
  const MAX_SOURCE_EXCERPT_CHARS = 2000;
@@ -301,6 +302,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
301
302
  constraintMarkdown,
302
303
  taskConfig,
303
304
  enabledExecutors: resolveEnabledExecutors(manifest.executors),
305
+ executorModelMatrix: resolveExecutorModelMatrices(manifest),
304
306
  verifyCommands,
305
307
  };
306
308
  }
@@ -336,18 +338,7 @@ export function buildStandardHybridDagFromTask(sources) {
336
338
  contextProfile: taskConfig.contextProfile,
337
339
  },
338
340
  skillsByRole: HYBRID_SKILLS_BY_ROLE,
339
- executorModels: {
340
- cursor: {
341
- LOW: "composer-2.5",
342
- MED: "composer-2.5",
343
- HIGH: "composer-2.5",
344
- },
345
- pi: {
346
- LOW: "gpt-5.3-codex-spark",
347
- MED: "glm-5.2",
348
- HIGH: "gpt-5.5",
349
- },
350
- },
341
+ executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
351
342
  tasks: [
352
343
  {
353
344
  id: "contract-pi",
@@ -42,6 +42,10 @@ function buildSkillCandidatePaths(input) {
42
42
  const piSkillsDir = path.join(input.homeDir, ".pi", "agent", "skills");
43
43
  candidates.push(normalizeCandidate(path.join(piSkillsDir, input.name, "SKILL.md")), normalizeCandidate(path.join(piSkillsDir, `${input.name}.md`)));
44
44
  candidates.push(normalizeCandidate(path.join(input.cwd, "skills", input.name, "SKILL.md")));
45
+ // Agent-compatible project path (e.g. OpenCode). `skills/` stays the primary
46
+ // loop-agent repo-local path; `.agents/skills` is searched after it and before
47
+ // bundled skills so a project with only the mirror can still resolve.
48
+ candidates.push(normalizeCandidate(path.join(input.cwd, ".agents", "skills", input.name, "SKILL.md")));
45
49
  if (input.name === "loop-agent") {
46
50
  candidates.push(normalizeCandidate(path.join(input.cwd, "skill", "SKILL.md")));
47
51
  }
@@ -251,7 +251,7 @@ export const DEFAULT_DAG_EXECUTOR_MODELS = {
251
251
  cursor: {
252
252
  LOW: "composer-2.5",
253
253
  MED: "composer-2.5",
254
- HIGH: "composer-2.5",
254
+ HIGH: "gpt-5.5",
255
255
  },
256
256
  pi: {
257
257
  LOW: "gpt-5.3-codex-spark",
@@ -428,7 +428,7 @@ function validateDecisionGateTaskConfig(task, issues) {
428
428
  });
429
429
  }
430
430
  }
431
- export function collectExecutorModelWarnings(spec) {
431
+ export function collectExecutorModelWarnings(spec, expectedMatrices = DEFAULT_DAG_EXECUTOR_MODELS) {
432
432
  const warnings = [];
433
433
  const executors = Object.keys(DEFAULT_DAG_EXECUTOR_MODELS);
434
434
  for (const executor of executors) {
@@ -436,11 +436,12 @@ export function collectExecutorModelWarnings(spec) {
436
436
  if (!actualByComplexity) {
437
437
  continue;
438
438
  }
439
+ const expectedByComplexity = expectedMatrices[executor] ?? DEFAULT_DAG_EXECUTOR_MODELS[executor];
439
440
  for (const [complexityKey, actual] of Object.entries(actualByComplexity)) {
440
441
  if (actual === undefined) {
441
442
  continue;
442
443
  }
443
- const expected = DEFAULT_DAG_EXECUTOR_MODELS[executor][complexityKey];
444
+ const expected = expectedByComplexity[complexityKey];
444
445
  if (actual !== expected) {
445
446
  warnings.push({
446
447
  executor,
package/docs/README.md CHANGED
@@ -14,8 +14,12 @@
14
14
  - `loop-agent-harness.md` — runtime 与 command surface 概览
15
15
  - `agent-dag-runner.md` — Agent DAG runner 指南
16
16
  - `cursor-executor-usage.md` — Cursor executor 用法
17
- - `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine 路线图与适配分析
18
- - `init-surface.manifest.json` — npm 包范围、目标项目初始化投影与 `init check-update` surface 分类的机器校验契约
17
+ - `dynamic-workflow-dag-engine-roadmap.md` — Dynamic Workflow DAG Engine 路线图与适配分析
18
+ - `init-surface.manifest.json` — npm 包范围、目标项目初始化投影与 `init check-update` surface 分类的机器校验契约
19
+
20
+ ## 设计思想来源
21
+
22
+ - `../website/docs/practices/` — Anthropic 长时运行 agent harness、OpenAI Codex harness engineering、腾讯端到端 Harness Engineering 与社区 agent harness 实践资料。当前仓库的“人类掌舵、智能体执行”、仓库即记录系统、小步增量、结构化 handoff 和 shell verification 纪律均受这些实践启发;权威执行规则仍以本目录治理文档、根目录 AGENTS.md、harness.json、skills 目录和脚本检查为准。
19
23
 
20
24
  ## 方法论
21
25
 
@@ -31,7 +35,7 @@
31
35
  - `progress/README.md` — 进度交接日志
32
36
  - `reports/README.md` — 验证与审计报告
33
37
  - `decisions/README.md` — 架构决策
34
- - `skills/README.md` — repo-local skill registry and vetting notes
38
+ - `skills/README.md` — repo-local skill registry and vetting notes
35
39
  - `templates/` — 可复用的规划、报告与 DAG 模板
36
40
 
37
41
  ## 仓库 Skills
@@ -46,10 +50,10 @@
46
50
  - `templates/sprint-contract.md` — 实现契约与验收标准
47
51
  - `templates/exec-plan.md` — 非平凡工作的执行计划
48
52
  - `templates/progress-log.md` — 进度与交接日志
49
- - `templates/qa-report.md` — 验证与 QA 证据
50
- - `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
51
- - `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
52
- - `templates/adr.md` — 架构决策记录(ADR)
53
+ - `templates/qa-report.md` — 验证与 QA 证据
54
+ - `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
55
+ - `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
56
+ - `templates/adr.md` — 架构决策记录(ADR)
53
57
 
54
58
  ## 维护
55
59
 
@@ -6,6 +6,8 @@ loop-agent 是面向 agentic coding 的工作流 runtime。仓库应保持小而
6
6
 
7
7
  项目采用「人类掌舵、智能体执行」的工程模型。Agent 可实现、验证与总结,但持久意图与完成证据必须落在仓库中。
8
8
 
9
+ 这个模型吸收了 Anthropic 长时运行 agent harness、OpenAI Codex harness engineering、腾讯端到端 Harness Engineering 和社区 agent harness 实践中的共同经验:用短入口文档导航长期知识,用结构化任务状态和 handoff artifact 跨 session 传递事实,用独立验证和评审回路约束 agent 输出,并把人类注意力集中在意图、边界、架构和反馈系统上。面向使用者的背景资料收录在 `website/docs/practices/`。
10
+
9
11
  - 仓库是记录系统。决策、契约、计划、测试、报告与交接属于 tracked files。
10
12
  - `AGENTS.md` 是操作地图,不是知识 dump。长期方法论与决策属于 `docs/`。
11
13
  - 工作以小步、可逆、可验证的增量推进。
@@ -4,4 +4,4 @@
4
4
 
5
5
  源码仓库可在本 README 旁保留具体 active plan 文件。npm 包只携带本 README 作为目录契约,不复制 loop-agent 源码历史的 active plan;目标仓库自行生成 active plan。
6
6
 
7
- 当前 active plan:无。
7
+ 当前没有 active plan。`2026-07-08-taskspec-worker-master.md` 的 7 个 step 已全部完成并归档到 `../completed/`。
@@ -9,3 +9,11 @@ npm 包携带本 README 作为目录契约。具体 completed plan 属于目标
9
9
  - [`2026-07-04-runtime-boundary-remediation.md`](2026-07-04-runtime-boundary-remediation.md) — 整合 CLI/skill/runtime 边界,抽出 DAG/Loop runtime seam,集中 harness store/guard 策略
10
10
  - [`2026-07-04-dag-role-skill-alignment.md`](2026-07-04-dag-role-skill-alignment.md) — 对齐 DAG/Dynamic Workflow role 与 repo-local vetted skills,新增 strict skill audit
11
11
  - [`2026-07-06-production-readiness-hardening.md`](2026-07-06-production-readiness-hardening.md) — 冻结 Production Readiness v0.1,打磨 DAG 主路径 next steps、failure routing、doctor/report/failure handoff 与 dogfood 验证
12
+ - [`2026-07-08-taskspec-schema-validate.md`](2026-07-08-taskspec-schema-validate.md) — 新增 TaskSpec v0.1 schema、三层校验器、risk→complexity 映射和 5 个 dogfood TaskSpec 样例
13
+ - [`2026-07-08-acceptance-taskgraph-ready.md`](2026-07-08-acceptance-taskgraph-ready.md) — 新增 AcceptanceSpec / TaskGraphSpec schema、Feature graph 校验、环检测和 Ready 队列计算
14
+ - [`2026-07-08-profile-mapping-agent-worker-cli.md`](2026-07-08-profile-mapping-agent-worker-cli.md) — 新增 business profile 到 loop-agent profile 映射、独立 `agent-worker` CLI 和 package bin surface
15
+ - [`2026-07-08-loop-agent-client-contract-smoke.md`](2026-07-08-loop-agent-client-contract-smoke.md) — 新增 LoopAgentClient、command artifact、JSON parse failure、repo resolver/preflight 与 Ring 0 CLI contract smoke
16
+ - [`2026-07-08-materializer-ring1-dag.md`](2026-07-08-materializer-ring1-dag.md) — 新增 TaskSpec materializer、materialize manifest、coverage-safe source 输出与 Ring 1 DAG generate/validate/dry-run smoke
17
+ - [`2026-07-08-run-task-pipeline-ring2.md`](2026-07-08-run-task-pipeline-ring2.md) — 新增 Worker run-task pipeline、task-local run record、成功/失败分流与 Ring 2 shell-only DAG success/failure smoke
18
+ - [`2026-07-08-task-pool-morning-report.md`](2026-07-08-task-pool-morning-report.md) — 新增 Worker Task Pool JSONL/state store、failure routing、串行 batch run-ready 和 morning report
19
+ - [`2026-07-08-taskspec-worker-master.md`](2026-07-08-taskspec-worker-master.md) — 完成 TaskSpec + Worker 7 步 master 编排,覆盖 schema、graph、profile、client、materialize、run-task、Task Pool 和 morning report