@tea-agent/loop-agent 0.38.2 → 0.38.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 +4 -1
- package/CHANGELOG.md +171 -96
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +76 -10
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +51 -12
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +265 -15
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
- package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
- package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +196 -117
- package/dist/workflows/dag/node-execution.js +139 -10
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/rerun-plan.js +134 -3
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +32 -1
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +2 -2
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
|
@@ -58,6 +58,7 @@ analysis_status: no-clarification-required
|
|
|
58
58
|
- 无未确认的推断需求。
|
|
59
59
|
### 4.3 待确认问题
|
|
60
60
|
- 无待确认问题。
|
|
61
|
+
- 一致性核对:已逐条比对知识库、ai_workspace、仓库规范与代码现状,未发现冲突条目。
|
|
61
62
|
- 理由:范围、权限和结果已经明确。
|
|
62
63
|
### 4.4 初步非目标
|
|
63
64
|
- 不修改头像。
|
|
@@ -277,7 +278,7 @@ expectPass("valid V4 analysis", analysisValidator, [analysisPath, "--target", "f
|
|
|
277
278
|
expectFail("analysis target mismatch", analysisValidator, [analysisPath, "--target", "backend"], "does not match");
|
|
278
279
|
expectFail("analysis rejects extra frontmatter fields", analysisValidator, [variant("analysis-extra-frontmatter.md", analysis.replace("analysis_status: no-clarification-required", "analysis_status: no-clarification-required\nsource_path: /private/customer/prd.md"))], "unsupported field source_path");
|
|
279
280
|
expectFail("analysis requires source identity", analysisValidator, [variant("analysis-no-source-identity.md", analysis.replace(`- 原始需求身份:${sourceIdentity}\n`, ""))], "原始需求身份");
|
|
280
|
-
const analysisWithKnowledgeHit = analysis.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001
|
|
281
|
+
const analysisWithKnowledgeHit = analysis.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001:采用项目统一空状态组件;来源为当前有效设计系统;与输入关系:补充。");
|
|
281
282
|
writeFileSync(analysisPath, analysisWithKnowledgeHit);
|
|
282
283
|
expectPass("analysis accepts executed-hit with KB fact", analysisValidator, [analysisPath, "--target", "frontend"]);
|
|
283
284
|
writeFileSync(analysisPath, analysis);
|
|
@@ -290,6 +291,11 @@ const analysisWithKnowledgeUnavailable = analysis.replace("- 检索状态:exec
|
|
|
290
291
|
writeFileSync(analysisPath, analysisWithKnowledgeUnavailable);
|
|
291
292
|
expectPass("analysis unavailable accepts user skip confirmation", analysisValidator, [analysisPath, "--target", "frontend"]);
|
|
292
293
|
writeFileSync(analysisPath, analysis);
|
|
294
|
+
expectFail("analysis fact must declare 与输入关系", analysisValidator, [variant("analysis-fact-no-relation.md", analysis.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001:采用项目统一空状态组件;来源为当前有效设计系统。"))], "must declare 与输入关系");
|
|
295
|
+
expectFail("analysis CODE-FACT must declare 证据类别", analysisValidator, [variant("analysis-code-fact-no-evidence-class.md", analysis.replace("- 未提供代码仓库。", "- CODE-FACT-001:昵称校验已在表单层实现;证据位置:表单校验;与输入关系:一致。"))], "must declare 证据类别");
|
|
296
|
+
expectFail("analysis conflict fact must be referenced by 待确认问题", analysisValidator, [variant("analysis-conflict-unreferenced.md", analysis.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001:昵称允许 1–30 字符;来源:昵称规范 §2;与输入关系:冲突。"))], "not referenced by 待确认问题");
|
|
297
|
+
expectFail("no-clarification analysis rejects conflict facts", analysisValidator, [variant("analysis-conflict-no-clarification.md", analysis.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001:昵称允许 1–30 字符;来源:昵称规范 §2;与输入关系:冲突。\n- P0:昵称长度冲突;冲突描述:输入声明「2–20 字符」vs 规范声明「1–30 字符」;冲突来源:KB-FACT-001;影响范围:frontend。"))], "cannot contain facts marked 与输入关系:冲突");
|
|
298
|
+
expectFail("no-clarification analysis requires 一致性核对 conclusion", analysisValidator, [variant("analysis-no-check-line.md", analysis.replace("- 一致性核对:已逐条比对知识库、ai_workspace、仓库规范与代码现状,未发现冲突条目。\n", ""))], "must record a 一致性核对 line");
|
|
293
299
|
expectFail("analysis rejects formal AC", analysisValidator, [variant("analysis-ac.md", analysis.replace("验收关注点:", "验收关注点:AC-FE-001 "))], "must use acceptance concerns");
|
|
294
300
|
expectFail("analysis requires matching spec", analysisValidator, [variant("analysis-no-spec.md", analysis.replace("### FE-US-001 修改昵称\n- 页面/组件", "### FE-US-002 修改昵称\n- 页面/组件"))], "missing output specification");
|
|
295
301
|
expectFail("analysis rejects standalone roles", analysisValidator, [variant("analysis-roles.md", analysis.replace("## 4. 需求分析", "## 用户角色\n- 已登录用户。\n## 4. 需求分析"))], "must not contain standalone");
|
|
@@ -488,6 +494,10 @@ expectPass("complete question clarification with DEC markers", clarificationVali
|
|
|
488
494
|
expectFail("complete clarification rejects unformed final decision", clarificationValidator, [analysisPath, variant("clarification-unformed-decision.md", completeQuestionClarification.replace("- 最终决策:保存成功后展示项目统一成功提示", "- 最终决策:未形成")), requirementPath], "must contain a formed final decision");
|
|
489
495
|
expectPass("requirement with DEC markers", requirementValidator, [requirementPath, "--target", "frontend"]);
|
|
490
496
|
expectFail("complete clarification requires shared-understanding marker", clarificationValidator, [analysisPath, variant("clarification-no-shared.md", completeQuestionClarification.replace("- 共同理解:已确认\n", "")), requirementPath], "共同理解:已确认");
|
|
497
|
+
const conflictReadyAnalysis = readyAnalysis
|
|
498
|
+
.replace("- P1:保存成功后使用哪种反馈?", "- P0:昵称长度冲突;冲突描述:输入声明「2–20 字符」vs 规范声明「1–30 字符」;冲突来源:KB-FACT-001;影响范围:frontend。")
|
|
499
|
+
.replace("- 检索状态:executed-no-match。", "- 检索状态:executed-hit。\n- KB-FACT-001:昵称允许 1–30 字符;来源:昵称规范 §2;与输入关系:冲突。");
|
|
500
|
+
expectFail("complete clarification must cover PA conflict facts", clarificationValidator, [variant("conflict-analysis-uncovered.md", conflictReadyAnalysis), clarificationPath, requirementPath], "must be covered by a Q-* record");
|
|
491
501
|
expectFail("analysis frontend output requires page route", analysisValidator, [variant("analysis-no-route.md", analysis.replace("- 页面路由:/account/profile\n", "")), "--target", "frontend"], "missing field 页面路由");
|
|
492
502
|
expectFail("analysis route must be a product path", analysisValidator, [variant("analysis-bad-route.md", analysis.replace("- 页面路由:/account/profile", "- 页面路由:ProfileRoute 组件")), "--target", "frontend"], "must start with /");
|
|
493
503
|
const embeddedRouteAnalysis = analysis.replace("- 页面路由:/account/profile", "- 页面路由:不适用;嵌入 /account/profile 页面");
|
|
@@ -54,11 +54,30 @@ const inferenceLines = contentLines(inferences);
|
|
|
54
54
|
const questionLines = contentLines(questions);
|
|
55
55
|
const noInferenceSentinel = /^-\s*无未确认的推断需求[。.]?$/.test(inferenceLines[0] ?? "");
|
|
56
56
|
const noQuestionSentinel = /^-\s*无待确认问题[。.]?$/.test(questionLines[0] ?? "");
|
|
57
|
-
const unexpectedQuestionLines = questionLines.slice(1).filter((line) => !/^-\s*理由[::]\s*\S/.test(line));
|
|
57
|
+
const unexpectedQuestionLines = questionLines.slice(1).filter((line) => !/^-\s*理由[::]\s*\S/.test(line) && !/^-\s*一致性核对[::]\s*\S/.test(line));
|
|
58
58
|
if (status === "no-clarification-required" && (!noInferenceSentinel || inferenceLines.length !== 1)) errors.push("no-clarification-required analysis must declare no unconfirmed inferred requirements.");
|
|
59
59
|
if (status === "no-clarification-required" && (!noQuestionSentinel || unexpectedQuestionLines.length)) errors.push("no-clarification-required analysis must declare no pending questions.");
|
|
60
60
|
if (status === "no-clarification-required" && /(?:\bQ-\d{3,}\b|\bBR-\d{3,}\b|\bP[012]\b|[??])/.test(questions)) errors.push("no-clarification-required analysis cannot contain question markers, priorities, branches, or question sentences.");
|
|
61
61
|
if (status === "ready-for-clarification" && !/(?:\bP[012]\b|[??])/.test(questions)) errors.push("ready-for-clarification analysis must contain at least one prioritized or explicit question.");
|
|
62
|
+
// 「输入↔规范一致性核对」检查:事实行格式、冲突标记与待确认问题引用
|
|
63
|
+
const factLines = facts.split(/\r?\n/).filter((line) => /^[ \t]*-[ \t]*(?:KB|CODE)-FACT-\d+\b/.test(line));
|
|
64
|
+
for (const line of factLines) {
|
|
65
|
+
const id = line.match(/\b(?:KB|CODE)-FACT-\d+\b/)?.[0];
|
|
66
|
+
if (!/与输入关系[::]\s*(?:一致|补充|冲突)/.test(line)) errors.push(`${id} must declare 与输入关系:一致|补充|冲突.`);
|
|
67
|
+
if (/^[ \t]*-[ \t]*CODE-FACT-\d+\b/.test(line) && !/证据类别[::]\s*(?:项目规范|代码现状)/.test(line)) errors.push(`${id} must declare 证据类别:项目规范|代码现状.`);
|
|
68
|
+
}
|
|
69
|
+
const conflictFactIds = factLines.filter((line) => /与输入关系[::]\s*冲突/.test(line)).map((line) => line.match(/\b(?:KB|CODE)-FACT-\d+\b/)?.[0]);
|
|
70
|
+
for (const id of conflictFactIds) {
|
|
71
|
+
if (!questions.includes(id)) errors.push(`${id} is marked 与输入关系:冲突 but is not referenced by 待确认问题.`);
|
|
72
|
+
}
|
|
73
|
+
const citedConflictIds = [...(questions.match(/冲突来源[::][^\n]*/g) ?? [])].flatMap((line) => line.match(/\b(?:KB|CODE)-FACT-\d+\b/g) ?? []);
|
|
74
|
+
for (const id of citedConflictIds) {
|
|
75
|
+
if (!conflictFactIds.includes(id)) errors.push(`${id} cited as 冲突来源 but its fact is not marked 与输入关系:冲突.`);
|
|
76
|
+
}
|
|
77
|
+
if (status === "no-clarification-required") {
|
|
78
|
+
if (conflictFactIds.length > 0) errors.push("no-clarification-required analysis cannot contain facts marked 与输入关系:冲突.");
|
|
79
|
+
if (!/^[ \t]*-[ \t]*一致性核对[::][^\n]*未发现冲突条目/m.test(questions)) errors.push("no-clarification-required analysis must record a 一致性核对 line stating no conflicts found.");
|
|
80
|
+
}
|
|
62
81
|
if (/\bAC-(?:FE|BE)-\d{3,}\b|^Given[::]|^When[::]|^Then[::]/m.test(text)) errors.push("Product Analysis must use acceptance concerns, not formal AC IDs or Given/When/Then blocks.");
|
|
63
82
|
if (/^##\s+(?:\d+[.、]?\s*)?(?:用户角色|初步验收标准汇总)\s*$/m.test(text)) errors.push("Product Analysis must not contain standalone 用户角色 or 初步验收标准汇总 sections.");
|
|
64
83
|
|
|
@@ -104,6 +104,15 @@ if (!questionBlocks.length) {
|
|
|
104
104
|
if (status === "complete" && !/^[ \t]*-[ \t]*共同理解[::][ \t]*已确认\s*$/m.test(section(clarification.text, 2, "决策索引") ?? "")) {
|
|
105
105
|
errors.push("Complete question-based clarification must record 共同理解:已确认 in 决策索引.");
|
|
106
106
|
}
|
|
107
|
+
// 「输入↔规范一致性核对」闭环:PA 待确认问题的冲突事实必须被 Q-* 覆盖
|
|
108
|
+
const analysisAnalysis = section(analysis.text, 2, "需求分析") ?? "";
|
|
109
|
+
const paQuestions = section(analysisAnalysis, 3, "待确认问题") ?? "";
|
|
110
|
+
const paConflictIds = [...(paQuestions.match(/冲突来源[::][^\n]*/g) ?? [])].flatMap((line) => line.match(/\b(?:KB|CODE)-FACT-\d+\b/g) ?? []);
|
|
111
|
+
for (const id of [...new Set(paConflictIds)]) {
|
|
112
|
+
const covering = questionBlocks.filter((block) => block.text.includes(id));
|
|
113
|
+
if (!covering.length) errors.push(`${id} referenced by Product Analysis pending questions must be covered by a Q-* record.`);
|
|
114
|
+
else if (status === "complete" && !covering.some((block) => (field(block.text, "状态") ?? "") === "confirmed")) errors.push(`${id} must be confirmed by an explicit user answer before clarification can be complete.`);
|
|
115
|
+
}
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
printResult("Requirement Clarification", clarification.path, errors);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fe-test-ui-scout
|
|
3
|
+
description: 在写 frontend-test Wave PRD/回归目录之前,对真实运行的被测页面做只读锚点侦察,产出并维护 testcase/frontend/rag/ui-anchors.md 锚点台账。用 playwright-cli snapshot/find 逐字面验证命中数与唯一性,防止 PRD 验收标准引用不存在或不唯一的文案。触发词:frontend-test、锚点、台账、ui-anchors、侦察、写 PRD 前、snapshot/find 校准、Wave PRD。
|
|
4
|
+
references:
|
|
5
|
+
- path: references/ledger-schema.md
|
|
6
|
+
required: true
|
|
7
|
+
- path: references/recon-protocol.md
|
|
8
|
+
required: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# FE-Test UI Scout(锚点侦察)
|
|
12
|
+
|
|
13
|
+
## 定位
|
|
14
|
+
|
|
15
|
+
写 Wave PRD / 校准回归目录 AC **之前**的前置侦察。产出锚点台账
|
|
16
|
+
`testcase/frontend/rag/ui-anchors.md`:每个「页面 × 状态」下真实可见、
|
|
17
|
+
`find` 可命中的字面清单。PRD 的每条 `find "..."` 断言必须能在台账找到对应行;
|
|
18
|
+
台账没有的字面禁止写进验收标准。
|
|
19
|
+
|
|
20
|
+
本 skill **不生成用例、不执行用例、不点任何写控件**。生成由
|
|
21
|
+
`playwright-cli-case-generator`(DAG generate 节点)负责,执行由
|
|
22
|
+
`playwright-cli`(DAG execute 节点)负责;本 skill 只在 DAG 之外的主会话使用。
|
|
23
|
+
|
|
24
|
+
## 输入
|
|
25
|
+
|
|
26
|
+
- 被测 baseUrl(来自 `testcase/frontend/rag/context.md` 的 `environmentProbe=reachable` URL)
|
|
27
|
+
- 目标 Wave 的 AC 清单(回归目录中本 Wave 范围的行)
|
|
28
|
+
- 既有台账(增量更新,不整表重写)
|
|
29
|
+
|
|
30
|
+
## 流程
|
|
31
|
+
|
|
32
|
+
1. **读取范围**:从回归目录取本 Wave AC 的「页面 + 测试点 + 验收标准」,
|
|
33
|
+
提取所有候选字面(引号内文案、aria-label、控件名)。
|
|
34
|
+
2. **逐状态侦察**:对每个涉及页面/状态组合:
|
|
35
|
+
`playwright-cli open --browser=chrome <baseUrl>/#/<route>` → `snapshot` →
|
|
36
|
+
逐候选 `find "<字面>"`,记录命中数与命中位置。
|
|
37
|
+
3. **记录陷阱**:拆节点(`<kbd>`+`<span>`、文本+`<code>`)、同名多控件、
|
|
38
|
+
aria-label 与可见文本不一致、状态前置(no-session / has-session / empty、
|
|
39
|
+
setup-required)都要写进备注列。
|
|
40
|
+
4. **增量写台账**:按 [references/ledger-schema.md](references/ledger-schema.md)
|
|
41
|
+
的表结构与分节规则追加/修订行,保留既有行历史(用备注标注失效原因,
|
|
42
|
+
不删行)。
|
|
43
|
+
5. **报告**:侦察完成后输出小结(新增/修订/失效行数、命中率、给 PRD 的
|
|
44
|
+
可用锚点清单与禁用清单)。
|
|
45
|
+
|
|
46
|
+
## 硬规则
|
|
47
|
+
|
|
48
|
+
- 只读:不点提交/保存/发送类控件、不创建会话以外的任何状态(「新建空白
|
|
49
|
+
对话」CTA 允许点击一次以进入 has-session 状态,属于台账明确的状态夹具)。
|
|
50
|
+
- 命中 0 或命中数 >1 且无法用状态/作用域区分的字面,标记 `不可断言`,
|
|
51
|
+
禁止进入 PRD `find` 断言。
|
|
52
|
+
- 台账每行必须带验证时间与当时 baseUrl;页面改版后旧行标 `失效` 而非删除。
|
|
53
|
+
- 不猜测:没跑过 `find` 的字面不进台账。
|
|
54
|
+
- PRD 正文(含背景/夹具段)不得出现会被需求 id 扫描捕获的形态:大写或小写的 REQ/BR/AC 前缀 + 连字符 + hash/序号(如夹具任务 id 里的 req 段、或自示范用的「req 加连字符 hash」字面)。夹具引用只写日期 + 短 hash(如 f03535aa),规则说明文字用中文描述或代码块包裹仍可能被扫——一律避免连写。
|
|
55
|
+
- PRD 验收标准只用**目录 AC id**(如 AC-FE-INS-005)作条目 id;不要写本地序号前缀(如 AC-003 加粗目录 id 的双 id 形态)——双 id 会被同时提取,生成器只引用其一,另一个落入 acceptanceCoverage.missing。
|
|
56
|
+
|
|
57
|
+
## 输出
|
|
58
|
+
|
|
59
|
+
- `testcase/frontend/rag/ui-anchors.md`(增量更新)
|
|
60
|
+
- 侦察小结(对话内输出,不落盘;结论回写 PRD 草稿的锚点引用)
|
|
61
|
+
|
|
62
|
+
## References
|
|
63
|
+
|
|
64
|
+
- [references/ledger-schema.md](references/ledger-schema.md) — 台账表结构与分节规则
|
|
65
|
+
- [references/recon-protocol.md](references/recon-protocol.md) — 逐状态侦察操作协议与陷阱清单
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 台账(ui-anchors.md)表结构与分节规则
|
|
2
|
+
|
|
3
|
+
## 文件位置与命名
|
|
4
|
+
|
|
5
|
+
- 路径:`testcase/frontend/rag/ui-anchors.md`
|
|
6
|
+
- 编码:UTF-8,LF;表格用 GitHub Flavored Markdown。
|
|
7
|
+
- 台账是**增量账本**:修订不删除历史行;失效行改状态列并注明原因。
|
|
8
|
+
|
|
9
|
+
## 顶层结构
|
|
10
|
+
|
|
11
|
+
```markdown
|
|
12
|
+
# UI 锚点台账(frontend-test)
|
|
13
|
+
|
|
14
|
+
- baseUrl:<验证时的绝对 URL>
|
|
15
|
+
- 更新时间:<YYYY-MM-DD HH:mm(本地时区)>
|
|
16
|
+
- 维护:fe-test-ui-scout skill
|
|
17
|
+
|
|
18
|
+
## <页面路由> · <状态>
|
|
19
|
+
|
|
20
|
+
| 字面(find 原文) | 命中 | 唯一 | 控件/位置 | 状态 | 备注 | 验证时间 |
|
|
21
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
22
|
+
| 后端研发(默认 DAG) | 1 | 是 | workflow-kind-select option | 有效 | 括号必须带全 | 2026-08-22 15:00 |
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 分节规则
|
|
26
|
+
|
|
27
|
+
- 一节 = 一个「页面路由 × 状态」组合。路由用 hash 形式(`#/tasks`、
|
|
28
|
+
`#/chat`、`/inspect/#/dags`);状态至少区分:
|
|
29
|
+
- `no-session`(无会话 landing)
|
|
30
|
+
- `has-session`(已存在会话,composer 可见)
|
|
31
|
+
- `empty-session`(新建空会话,无消息)
|
|
32
|
+
- 页面特有态(如 `#/night · 无计划`、`#/recovery · 无候选`、
|
|
33
|
+
setup-required)随需追加,节名写清楚。
|
|
34
|
+
- 同一页面多个状态各自成节;不要把跨状态结果合并进一行。
|
|
35
|
+
|
|
36
|
+
## 列语义
|
|
37
|
+
|
|
38
|
+
| 列 | 说明 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| 字面(find 原文) | `playwright-cli find "<字面>"` 的唯一入参原文,逐字节照抄(含括号、空格、中点) |
|
|
41
|
+
| 命中 | 侦察时 `find` 实际返回的匹配数(0/1/N) |
|
|
42
|
+
| 唯一 | 是/否/不适用;命中=1 且作用于目标控件才算「是」 |
|
|
43
|
+
| 控件/位置 | 命中元素的角色与位置(如 `h2`、`oc-suggestion strong`、`aria-label`) |
|
|
44
|
+
| 状态 | `有效` / `失效` / `待复核`;失效须在备注注明原因与替代字面 |
|
|
45
|
+
| 备注 | 陷阱与前置:拆节点(kbd+span、text+code)、同名多控件、aria-label≠可见文本、状态前置、setup 门 |
|
|
46
|
+
| 验证时间 | YYYY-MM-DD HH:mm |
|
|
47
|
+
|
|
48
|
+
## 有效 / 失效判定
|
|
49
|
+
|
|
50
|
+
- **有效**:命中=1、目标控件、字面在当前 build 下可复现。
|
|
51
|
+
- **不可断言**:命中 0(字面不存在或被拆节点)、命中 >1 且状态/作用域
|
|
52
|
+
无法区分(如「发送」同时命中 tips 与按钮)。这类行保留在台账里,
|
|
53
|
+
状态列写 `不可断言`,备注写原因——它们正是 PRD 的「禁用清单」。
|
|
54
|
+
- **失效**:页面改版后不再命中。不删行,改状态 + 备注(替代字面是什么)。
|
|
55
|
+
|
|
56
|
+
## 与 PRD / 目录的关系
|
|
57
|
+
|
|
58
|
+
- PRD 每条 `find "..."` 断言的字面必须出自台账某行且该行状态为
|
|
59
|
+
`有效`(或备注给出拆节点断言替代方案,如 `find "taskKind="` 与
|
|
60
|
+
`find "frontend-test"` 二选一)。
|
|
61
|
+
- 目录 AC 的 live 校准注记引用台账行(日期 + 路由+状态),保持
|
|
62
|
+
「AC ↔ 台账」双向可追溯。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 逐状态侦察操作协议与陷阱清单
|
|
2
|
+
|
|
3
|
+
## 前置检查
|
|
4
|
+
|
|
5
|
+
1. 确认 SUT 健康:`curl -s -o /dev/null -w '%{http_code}' <baseUrl>/api/health`
|
|
6
|
+
返回 200;非 200 先解决环境,不侦察。
|
|
7
|
+
2. 确认 SUT 跑的是当前 build(改过 `src/**` 后必须 `npm run build` 并重启
|
|
8
|
+
SUT,否则台账记录的是旧页面)。
|
|
9
|
+
3. 准备隔离 app-data(SUT 启动参数已带 `/tmp` 隔离目录时无需额外动作);
|
|
10
|
+
侦察结束把 no-session 状态还原(关闭页面即可;若点了「新建空白对话」
|
|
11
|
+
产生空会话,记录在案即可,不必清理 app-data)。
|
|
12
|
+
|
|
13
|
+
## 每个页面 × 状态的标准动作序列
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
playwright-cli open --browser=chrome <baseUrl>/#/<route>
|
|
17
|
+
playwright-cli snapshot
|
|
18
|
+
playwright-cli find "<候选字面 1>"
|
|
19
|
+
playwright-cli find "<候选字面 2>"
|
|
20
|
+
...
|
|
21
|
+
playwright-cli close
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `snapshot` 先行:从 snapshot 里核对候选字面的真实渲染形态(是否拆节点、
|
|
25
|
+
是否 aria-label、是否在折叠组内需先展开)。
|
|
26
|
+
- 每个候选字面必须真实 `find` 一次;snapshot 里「看起来有」不算数。
|
|
27
|
+
- 折叠组(如「工程边界(可选,人类确认)」):先记录折叠态的组标题字面,
|
|
28
|
+
再展开后侦察组内字段;两种形态分两行记。
|
|
29
|
+
- 需要状态夹具的节(has-session):点一次「新建空白对话」CTA 进入;
|
|
30
|
+
这是协议允许的唯一写动作。
|
|
31
|
+
|
|
32
|
+
## 陷阱清单(出现即写进备注列)
|
|
33
|
+
|
|
34
|
+
| 陷阱 | 症状 | 台账记法 |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| 拆节点 | `find "Enter 发送"` 0 命中;DOM 是 `<kbd>Enter</kbd><span>发送</span>` | 字面行记完整原文 + 命中 0 + 状态 `不可断言`;备注给出替代(`find "发送"` 或 `find "Enter"`)并说明拆法 |
|
|
37
|
+
| text+code 拆分 | `taskKind=frontend-test` 渲染为 `taskKind=` + `<code>frontend-test</code>` | 同上 |
|
|
38
|
+
| 同名多控件 | 「发送」同时命中 tips span 与发送按钮 aria-label | 命中 N>1,唯一=否;备注指明各自位置,PRD 需换更独特字面或限定状态 |
|
|
39
|
+
| 同名多行按钮(列表态) | 「重命名会话」在 3 行会话各自命中 1,全局命中 3;单会话态侦察时命中 1 | 备注标「会话数依赖」;断言法降级为 hover 目标行 → snapshot 解析该行真实 ref(存在即通过),禁止声明 find 命中数=1;或确保夹具为单行态(清 app-data) |
|
|
40
|
+
| aria-label ≠ 可见文本 | 控件可访问名是 aria-label,渲染文本不同 | 字面列记 aria-label 值,备注「aria-label;可见文本为 X」 |
|
|
41
|
+
| 无名 flex 子项 | flex 匿名文本无 name-from-content,find 不到 | 记录控件缺 aria-label → 这是**产品可修问题**,报告给修复决策树(页面象限),台账暂记 `不可断言` |
|
|
42
|
+
| 括号/全半角 | 「后端研发(默认 DAG)」少括号 0 命中 | 字面逐字节照抄;备注提醒 |
|
|
43
|
+
| 状态前置 | 控件只在 has-session / setup-required 等状态下存在 | 分节记录;PRD 引用时必须声明状态夹具 |
|
|
44
|
+
| 折叠组 | 字面在 `<details>` 内,未展开时 find 0 | 两种形态分行记录(折叠标题/展开后字段) |
|
|
45
|
+
| 瞬态文案 | 「正在加载…」类短暂存在 | 备注标「瞬态」,PRD 不引用 |
|
|
46
|
+
|
|
47
|
+
## 输出纪律
|
|
48
|
+
|
|
49
|
+
- find 命中数以工具返回为准,不目测。
|
|
50
|
+
- 每节侦察完立即写台账(不攒到最后凭记忆写)。
|
|
51
|
+
- 侦察小结按 SKILL.md 第 5 步输出:新增/修订/失效行数、命中率、
|
|
52
|
+
PRD 可用锚点清单与禁用清单。
|
|
53
|
+
- 发现疑似产品缺陷(白屏、文案缺失、aria 错误):记入小结的「移交修复」
|
|
54
|
+
段,不在台账内修,不因缺陷跳过其余候选字面的侦察。
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: improve-codebase-architecture
|
|
3
|
+
description: Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Improve Codebase Architecture
|
|
7
|
+
|
|
8
|
+
Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
|
|
9
|
+
|
|
10
|
+
## Glossary
|
|
11
|
+
|
|
12
|
+
Use these terms exactly in every suggestion. Consistent language is the point — don't drift into "component," "service," "API," or "boundary." Full definitions in [language.md](language.md).
|
|
13
|
+
|
|
14
|
+
- **Module** — anything with an interface and an implementation (function, class, package, slice).
|
|
15
|
+
- **Interface** — everything a caller must know to use the module: types, invariants, error modes, ordering, config. Not just the type signature.
|
|
16
|
+
- **Implementation** — the code inside.
|
|
17
|
+
- **Depth** — leverage at the interface: a lot of behaviour behind a small interface. **Deep** = high leverage. **Shallow** = interface nearly as complex as the implementation.
|
|
18
|
+
- **Seam** — where an interface lives; a place behaviour can be altered without editing in place. (Use this, not "boundary.")
|
|
19
|
+
- **Adapter** — a concrete thing satisfying an interface at a seam.
|
|
20
|
+
- **Leverage** — what callers get from depth.
|
|
21
|
+
- **Locality** — what maintainers get from depth: change, bugs, knowledge concentrated in one place.
|
|
22
|
+
|
|
23
|
+
Key principles (see [language.md](language.md) for the full list):
|
|
24
|
+
|
|
25
|
+
- **Deletion test**: imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
|
|
26
|
+
- **The interface is the test surface.**
|
|
27
|
+
- **One adapter = hypothetical seam. Two adapters = real seam.**
|
|
28
|
+
|
|
29
|
+
This skill is _informed_ by the project's domain model. The domain language gives names to good seams; ADRs record decisions the skill should not re-litigate.
|
|
30
|
+
|
|
31
|
+
## Process
|
|
32
|
+
|
|
33
|
+
### 1. Explore
|
|
34
|
+
|
|
35
|
+
Read the project's domain glossary and any ADRs in the area you're touching first.
|
|
36
|
+
|
|
37
|
+
Then use the Agent tool with `subagent_type=Explore` to walk the codebase. Don't follow rigid heuristics — explore organically and note where you experience friction:
|
|
38
|
+
|
|
39
|
+
- Where does understanding one concept require bouncing between many small modules?
|
|
40
|
+
- Where are modules **shallow** — interface nearly as complex as the implementation?
|
|
41
|
+
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
|
|
42
|
+
- Where do tightly-coupled modules leak across their seams?
|
|
43
|
+
- Which parts of the codebase are untested, or hard to test through their current interface?
|
|
44
|
+
|
|
45
|
+
Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.
|
|
46
|
+
|
|
47
|
+
### 2. Present candidates as an HTML report
|
|
48
|
+
|
|
49
|
+
Write a self-contained HTML file to the OS temp directory so nothing lands in the repo. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `<tmpdir>/architecture-review-<timestamp>.html` so each run gets a fresh file. Open it for the user — `xdg-open <path>` on Linux, `open <path>` on macOS, `start <path>` on Windows — and tell them the absolute path.
|
|
50
|
+
|
|
51
|
+
The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual.
|
|
52
|
+
|
|
53
|
+
For each candidate, the same template as before, but rendered as a card:
|
|
54
|
+
|
|
55
|
+
- **Files** — which files/modules are involved
|
|
56
|
+
- **Problem** — why the current architecture is causing friction
|
|
57
|
+
- **Solution** — plain English description of what would change
|
|
58
|
+
- **Benefits** — explained in terms of locality and leverage, and how tests would improve
|
|
59
|
+
- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening
|
|
60
|
+
- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge
|
|
61
|
+
|
|
62
|
+
End the report with a **Top recommendation** section: which candidate you'd tackle first and why.
|
|
63
|
+
|
|
64
|
+
**Use CONTEXT.md vocabulary for the domain, and [language.md](language.md) vocabulary for the architecture.** If `CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service."
|
|
65
|
+
|
|
66
|
+
**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
|
|
67
|
+
|
|
68
|
+
See [html-report.md](html-report.md) for the full HTML scaffold, diagram patterns, and styling guidance.
|
|
69
|
+
|
|
70
|
+
Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?"
|
|
71
|
+
|
|
72
|
+
### 3. Grilling loop
|
|
73
|
+
|
|
74
|
+
Once the user picks a candidate, drop into a grilling conversation. Walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
|
|
75
|
+
|
|
76
|
+
Side effects happen inline as decisions crystallize:
|
|
77
|
+
|
|
78
|
+
- **Naming a deepened module after a concept not in `CONTEXT.md`?** Add the term to `CONTEXT.md` — same discipline as `/grill-with-docs` (see [context-format.md](../grill-with-docs/context-format.md)). Create the file lazily if it doesn't exist.
|
|
79
|
+
- **Sharpening a fuzzy term during the conversation?** Update `CONTEXT.md` right there.
|
|
80
|
+
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones. See [adr-format.md](../grill-with-docs/adr-format.md).
|
|
81
|
+
- **Want to explore alternative interfaces for the deepened module?** See [interface-design.md](interface-design.md).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Deepening
|
|
2
|
+
|
|
3
|
+
How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [language.md](language.md) — **module**, **interface**, **seam**, **adapter**.
|
|
4
|
+
|
|
5
|
+
## Dependency categories
|
|
6
|
+
|
|
7
|
+
When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam.
|
|
8
|
+
|
|
9
|
+
### 1. In-process
|
|
10
|
+
|
|
11
|
+
Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed.
|
|
12
|
+
|
|
13
|
+
### 2. Local-substitutable
|
|
14
|
+
|
|
15
|
+
Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface.
|
|
16
|
+
|
|
17
|
+
### 3. Remote but owned (Ports & Adapters)
|
|
18
|
+
|
|
19
|
+
Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter.
|
|
20
|
+
|
|
21
|
+
Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."*
|
|
22
|
+
|
|
23
|
+
### 4. True external (Mock)
|
|
24
|
+
|
|
25
|
+
Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter.
|
|
26
|
+
|
|
27
|
+
## Seam discipline
|
|
28
|
+
|
|
29
|
+
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection.
|
|
30
|
+
- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them.
|
|
31
|
+
|
|
32
|
+
## Testing strategy: replace, don't layer
|
|
33
|
+
|
|
34
|
+
- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them.
|
|
35
|
+
- Write new tests at the deepened module's interface. The **interface is the test surface**.
|
|
36
|
+
- Tests assert on observable outcomes through the interface, not internal state.
|
|
37
|
+
- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# HTML Report Format
|
|
2
|
+
|
|
3
|
+
The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic.
|
|
4
|
+
|
|
5
|
+
## Scaffold
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8" />
|
|
12
|
+
<title>Architecture review — {{repo name}}</title>
|
|
13
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
14
|
+
<script type="module">
|
|
15
|
+
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
16
|
+
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
|
17
|
+
</script>
|
|
18
|
+
<style>
|
|
19
|
+
/* small custom layer for things Tailwind doesn't cover cleanly:
|
|
20
|
+
dashed seam lines, hand-drawn-feeling arrow heads, etc. */
|
|
21
|
+
.seam { stroke-dasharray: 4 4; }
|
|
22
|
+
.leak { stroke: #dc2626; }
|
|
23
|
+
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body class="bg-stone-50 text-slate-900 font-sans">
|
|
27
|
+
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
|
|
28
|
+
<header>...</header>
|
|
29
|
+
<section id="candidates" class="space-y-10">...</section>
|
|
30
|
+
<section id="top-recommendation">...</section>
|
|
31
|
+
</main>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Header
|
|
37
|
+
|
|
38
|
+
Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates.
|
|
39
|
+
|
|
40
|
+
## Candidate card
|
|
41
|
+
|
|
42
|
+
The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms ([language.md](language.md)) without ceremony.
|
|
43
|
+
|
|
44
|
+
Each candidate is one `<article>`:
|
|
45
|
+
|
|
46
|
+
- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline").
|
|
47
|
+
- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`).
|
|
48
|
+
- **Files** — monospaced list, `font-mono text-sm`.
|
|
49
|
+
- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below.
|
|
50
|
+
- **Problem** — one sentence. What hurts.
|
|
51
|
+
- **Solution** — one sentence. What changes.
|
|
52
|
+
- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers".
|
|
53
|
+
- **ADR callout** (if applicable) — one line in an amber-tinted box.
|
|
54
|
+
|
|
55
|
+
No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram.
|
|
56
|
+
|
|
57
|
+
## Diagram patterns
|
|
58
|
+
|
|
59
|
+
Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point.
|
|
60
|
+
|
|
61
|
+
### Mermaid graph (the workhorse for dependencies / call flow)
|
|
62
|
+
|
|
63
|
+
Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1."
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
|
67
|
+
<pre class="mermaid">
|
|
68
|
+
flowchart LR
|
|
69
|
+
A[OrderHandler] --> B[OrderValidator]
|
|
70
|
+
B --> C[OrderRepo]
|
|
71
|
+
C -.leak.-> D[PricingClient]
|
|
72
|
+
classDef leak stroke:#dc2626,stroke-width:2px;
|
|
73
|
+
class C,D leak
|
|
74
|
+
</pre>
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Hand-built boxes-and-arrows (when Mermaid's layout fights you)
|
|
79
|
+
|
|
80
|
+
Modules as `<div>`s with borders and labels. Arrows as inline SVG `<line>` or `<path>` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight.
|
|
81
|
+
|
|
82
|
+
### Cross-section (good for layered shallowness)
|
|
83
|
+
|
|
84
|
+
Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility.
|
|
85
|
+
|
|
86
|
+
### Mass diagram (good for "interface as wide as implementation")
|
|
87
|
+
|
|
88
|
+
Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep).
|
|
89
|
+
|
|
90
|
+
### Call-graph collapse
|
|
91
|
+
|
|
92
|
+
Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it.
|
|
93
|
+
|
|
94
|
+
## Style guidance
|
|
95
|
+
|
|
96
|
+
- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate).
|
|
97
|
+
- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings.
|
|
98
|
+
- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling.
|
|
99
|
+
- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI.
|
|
100
|
+
- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering.
|
|
101
|
+
|
|
102
|
+
## Top recommendation section
|
|
103
|
+
|
|
104
|
+
One larger card. Candidate name, one sentence on why, anchor link to its card. That's it.
|
|
105
|
+
|
|
106
|
+
## Tone
|
|
107
|
+
|
|
108
|
+
Plain English, concise — but the architectural nouns and verbs come straight from [language.md](language.md). Concision is not an excuse to drift.
|
|
109
|
+
|
|
110
|
+
**Use exactly:** module, interface, implementation, depth, deep, shallow, seam, adapter, leverage, locality.
|
|
111
|
+
|
|
112
|
+
**Never substitute:** component, service, unit (for module) · API, signature (for interface) · boundary (for seam) · layer, wrapper (for module, when you mean module).
|
|
113
|
+
|
|
114
|
+
**Phrasings that fit the style:**
|
|
115
|
+
|
|
116
|
+
- "Order intake module is shallow — interface nearly matches the implementation."
|
|
117
|
+
- "Pricing leaks across the seam."
|
|
118
|
+
- "Deepen: one interface, one place to test."
|
|
119
|
+
- "Two adapters justify the seam: HTTP in prod, in-memory in tests."
|
|
120
|
+
|
|
121
|
+
**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place.
|
|
122
|
+
|
|
123
|
+
No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in [language.md](language.md), reach for one that is before inventing a new one.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Interface Design
|
|
2
|
+
|
|
3
|
+
When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best.
|
|
4
|
+
|
|
5
|
+
Uses the vocabulary in [language.md](language.md) — **module**, **interface**, **seam**, **adapter**, **leverage**.
|
|
6
|
+
|
|
7
|
+
## Process
|
|
8
|
+
|
|
9
|
+
### 1. Frame the problem space
|
|
10
|
+
|
|
11
|
+
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
|
|
12
|
+
|
|
13
|
+
- The constraints any new interface would need to satisfy
|
|
14
|
+
- The dependencies it would rely on, and which category they fall into (see [deepening.md](deepening.md))
|
|
15
|
+
- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete
|
|
16
|
+
|
|
17
|
+
Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel.
|
|
18
|
+
|
|
19
|
+
### 2. Spawn sub-agents
|
|
20
|
+
|
|
21
|
+
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
|
|
22
|
+
|
|
23
|
+
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [deepening.md](deepening.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
|
|
24
|
+
|
|
25
|
+
- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point."
|
|
26
|
+
- Agent 2: "Maximise flexibility — support many use cases and extension."
|
|
27
|
+
- Agent 3: "Optimise for the most common caller — make the default case trivial."
|
|
28
|
+
- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies."
|
|
29
|
+
|
|
30
|
+
Include both [language.md](language.md) vocabulary and CONTEXT.md vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language.
|
|
31
|
+
|
|
32
|
+
Each sub-agent outputs:
|
|
33
|
+
|
|
34
|
+
1. Interface (types, methods, params — plus invariants, ordering, error modes)
|
|
35
|
+
2. Usage example showing how callers use it
|
|
36
|
+
3. What the implementation hides behind the seam
|
|
37
|
+
4. Dependency strategy and adapters (see [deepening.md](deepening.md))
|
|
38
|
+
5. Trade-offs — where leverage is high, where it's thin
|
|
39
|
+
|
|
40
|
+
### 3. Present and compare
|
|
41
|
+
|
|
42
|
+
Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**.
|
|
43
|
+
|
|
44
|
+
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu.
|