@tea-agent/loop-agent 0.24.3 → 0.24.4

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 CHANGED
@@ -64,13 +64,21 @@
64
64
 
65
65
  ## 验证
66
66
 
67
- 权威源:`docs/governance/verification-matrix.md`。常用:
67
+ 权威源:`docs/governance/verification-matrix.md`。
68
+
69
+ 默认节奏:编辑中只跑矩阵「最低验证」(typecheck + 定向 Vitest);`git commit` 的 `pre-commit` 只做 `check-repo`;`git push` 的 `pre-push` 跑 `bash scripts/ci.sh`(全量 typecheck + `npm test`)。每个 clone 安装一次 hooks:`bash scripts/install-git-hooks.sh`。
70
+
71
+ 常用:
68
72
 
69
73
  ```bash
70
- npm run typecheck && npm test && npm run build
71
- node bin/loop-agent.js --help
72
- bash scripts/check-repo.sh
74
+ # 编辑中
75
+ npm run typecheck
76
+ npx vitest run <相关测试路径>
77
+
78
+ # push / 交付前(或依赖 pre-push)
73
79
  bash scripts/ci.sh
80
+ npm run build
81
+ node bin/loop-agent.js --help
74
82
  ```
75
83
 
76
84
  文档站变更:`npm run docs:build`。init / architecture / skill entry / pack 定向验证见 verification-matrix。
package/CHANGELOG.md CHANGED
@@ -2,14 +2,29 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [0.24.3] - 2026-07-28
5
+ ## [0.24.4] - 2026-07-28
6
6
 
7
7
  ### 重点更新
8
8
 
9
- - 发布工作流改为发布已通过完整门禁并经 SHA-256 校验的 npm tarball,避免 `npm publish` 再次触发全量测试、类型检查与构建。
9
+ - 控制台「导入需求」支持多文件批量导入,自动合成可编辑正文后创建任务
10
+ - 后端测试移除重复运行稳定性门槛,每次执行同时生成用户可读的 HTML 与 Markdown 报告
11
+ - 本地 pre-push 钩子升级为执行完整 CI 流程,覆盖治理检查、类型检查与全量测试
12
+
13
+ ### 新增
14
+
15
+ - 控制台「导入需求」支持选择或拖入一份或多份 Markdown/TXT 文档(严格 UTF-8,单文件 ≤8 MiB,最多 8 个),多文件分卷归档并合成可编辑正文后创建任务
10
16
 
11
17
  ### 改进
12
18
 
19
+ - 后端测试 L-5 结论不再将重复运行稳定性作为门槛,每次执行同时产出用户可读的 HTML 与 Markdown 报告,内部 facts 文件保留用于审计
20
+ - 本地 pre-push 钩子改为执行完整 CI 流程(治理检查 + 类型检查 + 全量测试),pre-commit 保持仅执行 check-repo
21
+ - harness 中等复杂度(MED)模型调整为 grok-4.5,并同步对齐相关 smoke/init 断言
22
+ - 归档已完成的设计文档与执行计划,刷新 0.24.x 六个月规划与能力摘要,并同步验证与 hooks 文档
23
+
24
+ ## [0.24.3] - 2026-07-28
25
+
26
+ ### 重点更新
27
+
13
28
  - 发布工作流改为发布已通过完整门禁并经 SHA-256 校验的 npm tarball,避免 `npm publish` 再次触发全量测试、类型检查与构建。
14
29
 
15
30
  ## [0.24.2] - 2026-07-28
@@ -1490,9 +1490,14 @@ async function allocateUniqueTaskId(repoRoot, preferredId) {
1490
1490
  }
1491
1491
  async function dispatchBootstrapFromPrd(ctx, p) {
1492
1492
  const content = str(p.content) ?? str(p.fileText);
1493
- if (!content) {
1493
+ if (!content?.trim()) {
1494
1494
  return invalid("bootstrapFromPrd", "content is required");
1495
1495
  }
1496
+ const parsedDocs = parseBootstrapDocuments(p.documents);
1497
+ if (!parsedDocs.ok) {
1498
+ return invalid("bootstrapFromPrd", parsedDocs.message);
1499
+ }
1500
+ const documents = parsedDocs.documents;
1496
1501
  const taskKind = normalizeConsoleWorkflowKind(str(p.taskKind), "standard");
1497
1502
  const identity = deriveTaskIdentityFromPrd(content);
1498
1503
  let taskId;
@@ -1539,28 +1544,86 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1539
1544
  }),
1540
1545
  };
1541
1546
  }
1542
- const staged = await stageUtf8Text(ctx.appData, content, {
1543
- extension: ".md",
1544
- });
1545
- const imported = await run(["import-prd", taskId, "--file", staged.path, "--json"], {
1546
- cwd: ctx.repoRoot,
1547
- artifactName: "bootstrap-import-prd",
1548
- expectJson: true,
1549
- timeoutMs: 60_000,
1550
- });
1551
- if (!imported.ok || imported.exitCode !== 0) {
1552
- return {
1553
- kind: "error",
1554
- status: 400,
1555
- body: operatorFailed({
1556
- command: "bootstrapFromPrd",
1557
- outcome: "rejected",
1558
- code: "INTERNAL_ERROR",
1559
- message: imported.stderr?.trim() ||
1560
- `import-prd failed with exit ${imported.exitCode}`,
1561
- details: { exitCode: imported.exitCode, taskId },
1562
- }),
1563
- };
1547
+ const succeededImports = [];
1548
+ const importResults = [];
1549
+ const runImportPrd = async (label, text, name) => {
1550
+ const staged = await stageUtf8Text(ctx.appData, text, {
1551
+ extension: ".md",
1552
+ });
1553
+ const imported = await run([
1554
+ "import-prd",
1555
+ taskId,
1556
+ "--file",
1557
+ staged.path,
1558
+ "--name",
1559
+ name,
1560
+ "--role",
1561
+ "requirement",
1562
+ "--json",
1563
+ ], {
1564
+ cwd: ctx.repoRoot,
1565
+ artifactName: `bootstrap-import-prd-${label}`,
1566
+ expectJson: true,
1567
+ timeoutMs: 60_000,
1568
+ });
1569
+ if (!imported.ok || imported.exitCode !== 0) {
1570
+ return {
1571
+ kind: "error",
1572
+ status: 400,
1573
+ body: operatorFailed({
1574
+ command: "bootstrapFromPrd",
1575
+ outcome: "rejected",
1576
+ code: "INTERNAL_ERROR",
1577
+ message: imported.stderr?.trim() ||
1578
+ `import-prd failed for "${name}" with exit ${imported.exitCode}. 任务已创建但不完整,请用 CLI 补 import-prd 或换 taskId 重试。`,
1579
+ details: {
1580
+ exitCode: imported.exitCode,
1581
+ taskId,
1582
+ failedName: name,
1583
+ succeededImports: [...succeededImports],
1584
+ },
1585
+ }),
1586
+ };
1587
+ }
1588
+ succeededImports.push(name);
1589
+ importResults.push(imported.json ?? null);
1590
+ return null;
1591
+ };
1592
+ if (documents && documents.length > 0) {
1593
+ for (const doc of documents) {
1594
+ const failure = await runImportPrd(doc.name, doc.content, doc.name);
1595
+ if (failure)
1596
+ return failure;
1597
+ }
1598
+ const composedFailure = await runImportPrd("composed", content, "composed");
1599
+ if (composedFailure)
1600
+ return composedFailure;
1601
+ }
1602
+ else {
1603
+ const staged = await stageUtf8Text(ctx.appData, content, {
1604
+ extension: ".md",
1605
+ });
1606
+ const imported = await run(["import-prd", taskId, "--file", staged.path, "--json"], {
1607
+ cwd: ctx.repoRoot,
1608
+ artifactName: "bootstrap-import-prd",
1609
+ expectJson: true,
1610
+ timeoutMs: 60_000,
1611
+ });
1612
+ if (!imported.ok || imported.exitCode !== 0) {
1613
+ return {
1614
+ kind: "error",
1615
+ status: 400,
1616
+ body: operatorFailed({
1617
+ command: "bootstrapFromPrd",
1618
+ outcome: "rejected",
1619
+ code: "INTERNAL_ERROR",
1620
+ message: imported.stderr?.trim() ||
1621
+ `import-prd failed with exit ${imported.exitCode}`,
1622
+ details: { exitCode: imported.exitCode, taskId },
1623
+ }),
1624
+ };
1625
+ }
1626
+ importResults.push(imported.json ?? null);
1564
1627
  }
1565
1628
  const draftSaved = await ctx.drafts.save({
1566
1629
  ...emptyDraft(taskId, title, { taskKind }),
@@ -1578,10 +1641,58 @@ async function dispatchBootstrapFromPrd(ctx, p) {
1578
1641
  identitySource: identity.source,
1579
1642
  preferredTaskId: identity.taskId,
1580
1643
  draftSha256: draftSaved.draftSha256,
1581
- import: imported.json ?? null,
1644
+ import: importResults.length === 1 ? importResults[0] : null,
1645
+ imports: importResults,
1646
+ documentNames: documents && documents.length > 0
1647
+ ? [...documents.map((d) => d.name), "composed"]
1648
+ : undefined,
1582
1649
  }),
1583
1650
  };
1584
1651
  }
1652
+ const MAX_BOOTSTRAP_DOCUMENTS = 8;
1653
+ function parseBootstrapDocuments(raw) {
1654
+ if (raw === undefined || raw === null) {
1655
+ return { ok: true, documents: null };
1656
+ }
1657
+ if (!Array.isArray(raw)) {
1658
+ return { ok: false, message: "documents must be an array" };
1659
+ }
1660
+ if (raw.length === 0) {
1661
+ return { ok: true, documents: null };
1662
+ }
1663
+ if (raw.length > MAX_BOOTSTRAP_DOCUMENTS) {
1664
+ return {
1665
+ ok: false,
1666
+ message: `documents supports at most ${MAX_BOOTSTRAP_DOCUMENTS} entries`,
1667
+ };
1668
+ }
1669
+ const documents = [];
1670
+ for (let i = 0; i < raw.length; i += 1) {
1671
+ const entry = raw[i];
1672
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
1673
+ return { ok: false, message: `documents[${i}] must be an object` };
1674
+ }
1675
+ const rec = entry;
1676
+ const name = str(rec.name)?.trim();
1677
+ const content = str(rec.content);
1678
+ const fileName = str(rec.fileName)?.trim();
1679
+ if (!name) {
1680
+ return { ok: false, message: `documents[${i}].name is required` };
1681
+ }
1682
+ if (!content?.trim()) {
1683
+ return {
1684
+ ok: false,
1685
+ message: `documents[${i}].content is required`,
1686
+ };
1687
+ }
1688
+ documents.push({
1689
+ name,
1690
+ content,
1691
+ ...(fileName ? { fileName } : {}),
1692
+ });
1693
+ }
1694
+ return { ok: true, documents };
1695
+ }
1585
1696
  async function ensureDraft(ctx, taskId, title, options) {
1586
1697
  const existing = await ctx.drafts.get(taskId);
1587
1698
  if (existing) {