@tea-agent/loop-agent 0.13.0-beta.0 → 0.13.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 (282) hide show
  1. package/AGENTS.md +157 -155
  2. package/CHANGELOG.md +301 -322
  3. package/README.md +335 -345
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/commands/cursor-prompt.js +6 -6
  7. package/dist/commands/init.js +597 -528
  8. package/dist/commands/loop-benchmark.js +11 -11
  9. package/dist/commands/pi-reuse-benchmark.js +16 -16
  10. package/dist/executors/shell-executor.js +200 -21
  11. package/dist/infrastructure/evaluation/candidate-store.js +5 -1
  12. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  13. package/dist/task/runtime.js +27 -27
  14. package/dist/worker/observe/static/api.js +46 -46
  15. package/dist/worker/observe/static/app.js +150 -150
  16. package/dist/worker/observe/static/constants.js +148 -148
  17. package/dist/worker/observe/static/copy.js +67 -67
  18. package/dist/worker/observe/static/dag-helpers.js +172 -172
  19. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  20. package/dist/worker/observe/static/dag-layout.js +83 -83
  21. package/dist/worker/observe/static/dag-model.js +72 -72
  22. package/dist/worker/observe/static/dom.js +212 -53
  23. package/dist/worker/observe/static/format-pool.js +67 -67
  24. package/dist/worker/observe/static/format.js +292 -292
  25. package/dist/worker/observe/static/index.html +308 -308
  26. package/dist/worker/observe/static/kpi.js +94 -94
  27. package/dist/worker/observe/static/relations.js +133 -133
  28. package/dist/worker/observe/static/router.js +93 -93
  29. package/dist/worker/observe/static/run-processing.js +148 -148
  30. package/dist/worker/observe/static/shell-chrome.js +68 -68
  31. package/dist/worker/observe/static/state.js +267 -253
  32. package/dist/worker/observe/static/styles.css +1902 -1902
  33. package/dist/worker/observe/static/views/batch.js +227 -227
  34. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  35. package/dist/worker/observe/static/views/dag-inspector.js +627 -607
  36. package/dist/worker/observe/static/views/dag.js +371 -362
  37. package/dist/worker/observe/static/views/dashboard.js +509 -252
  38. package/dist/worker/observe/static/views/failures.js +143 -143
  39. package/dist/worker/observe/static/views/feature.js +492 -492
  40. package/dist/worker/observe/static/views/pool.js +350 -350
  41. package/dist/worker/observe/static/views/run.js +453 -453
  42. package/dist/worker/observe/static/views/session-timeline.js +219 -205
  43. package/dist/worker/observe/static/views/shell.js +7 -7
  44. package/dist/worker/observe/static/views/task.js +314 -314
  45. package/dist/worker/observe/static/views/timeline.js +163 -163
  46. package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
  47. package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
  48. package/dist/workflows/dag/backend-test-result-contract.js +568 -0
  49. package/dist/workflows/dag/canvas-observer.js +275 -275
  50. package/dist/workflows/dag/decision-envelope.js +57 -2
  51. package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
  52. package/dist/workflows/dag/frontend-project-capability.js +309 -0
  53. package/dist/workflows/dag/frontend-repair.js +341 -0
  54. package/dist/workflows/dag/frontend-risk.js +161 -0
  55. package/dist/workflows/dag/frontend-verification-trace.js +190 -0
  56. package/dist/workflows/dag/init-hybrid.js +1020 -125
  57. package/dist/workflows/dag/repair-artifact.js +43 -3
  58. package/dist/workflows/dag/skill-instructions.js +4 -2
  59. package/dist/workflows/dag/types.js +29 -8
  60. package/docs/README.md +105 -104
  61. package/docs/agent-dag-recovery-playbook.md +195 -195
  62. package/docs/agent-dag-runner.md +67 -67
  63. package/docs/architecture/README.md +26 -26
  64. package/docs/architecture/dag-execution.md +140 -140
  65. package/docs/architecture/evolution.md +54 -54
  66. package/docs/architecture/facts-and-state.md +71 -71
  67. package/docs/architecture/runtime-boundaries.md +191 -191
  68. package/docs/architecture/system-overview.md +93 -93
  69. package/docs/architecture/worker-and-feature.md +85 -85
  70. package/docs/cursor-prompt-sidecar.md +36 -36
  71. package/docs/decisions/README.md +18 -18
  72. package/docs/design/README.md +167 -167
  73. package/docs/development-principles.md +73 -73
  74. package/docs/exec-plans/README.md +6 -6
  75. package/docs/exec-plans/active/README.md +1 -4
  76. package/docs/exec-plans/completed/README.md +106 -84
  77. package/docs/feature-workflow.md +414 -389
  78. package/docs/harness-methodology-debugging.md +153 -153
  79. package/docs/harness-methodology-tdd.md +130 -130
  80. package/docs/harness-methodology-verification.md +27 -27
  81. package/docs/init-surface.manifest.json +307 -289
  82. package/docs/loop-agent-harness.md +142 -142
  83. package/docs/production-readiness.md +96 -96
  84. package/docs/progress/README.md +76 -60
  85. package/docs/reports/README.md +150 -108
  86. package/docs/skills/README.md +7 -7
  87. package/docs/skills/vetted-skill-registry.md +29 -29
  88. package/docs/templates/adr.md +60 -60
  89. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  90. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  91. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  92. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  93. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  94. package/docs/templates/agent-dag-report.schema.json +473 -473
  95. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  96. package/docs/templates/agent-dag.base.json +190 -190
  97. package/docs/templates/agent-dag.final-verification.json +185 -185
  98. package/docs/templates/agent-dag.schema.json +411 -411
  99. package/docs/templates/agent-dag.supervised-implementation.json +620 -501
  100. package/docs/templates/backend-test-analysis.schema.json +44 -44
  101. package/docs/templates/backend-test-case-manifest.schema.json +190 -0
  102. package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
  103. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +204 -202
  104. package/docs/templates/backend-test-dag.json +559 -311
  105. package/docs/templates/backend-test-dag.retrospect.prompt.md +139 -125
  106. package/docs/templates/backend-test-dag.review-cases.prompt.md +83 -81
  107. package/docs/templates/backend-test-execution.schema.json +133 -0
  108. package/docs/templates/backend-test-result.schema.json +99 -0
  109. package/docs/templates/branch-merge-report.md +93 -0
  110. package/docs/templates/exec-plan.md +64 -64
  111. package/docs/templates/feature-spec.md +53 -53
  112. package/docs/templates/frontend-design-contract.md +42 -42
  113. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
  114. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
  115. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
  116. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
  117. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
  118. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
  119. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
  120. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
  121. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
  122. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
  123. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
  124. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
  125. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
  126. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
  127. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
  128. package/docs/templates/frontend-eval/metrics.md +138 -0
  129. package/docs/templates/frontend-eval/smoke-targets.md +53 -0
  130. package/docs/templates/frontend-implementation-contract.schema.json +27 -0
  131. package/docs/templates/frontend-task-constraints.md +35 -35
  132. package/docs/templates/frontend-task-requirement.md +70 -70
  133. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -5
  134. package/docs/templates/frontend-test-dag.json +23 -23
  135. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -3
  136. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -3
  137. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -3
  138. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -3
  139. package/docs/templates/harness.schema.json +221 -221
  140. package/docs/templates/hybrid-dag.json +188 -188
  141. package/docs/templates/init-evolution-review.md +35 -35
  142. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  143. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  144. package/docs/templates/knowledge-sync-dag.json +178 -178
  145. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  146. package/docs/templates/product-line/AGENTS.md +8 -8
  147. package/docs/templates/product-line/README.md +9 -9
  148. package/docs/templates/product-line/acceptance.yaml +14 -14
  149. package/docs/templates/product-line/closeout.yaml +9 -9
  150. package/docs/templates/product-line/design.md +13 -13
  151. package/docs/templates/product-line/links.md +10 -10
  152. package/docs/templates/product-line/requirement.md +17 -17
  153. package/docs/templates/product-line/task-graph.yaml +15 -15
  154. package/docs/templates/product-line/task.yaml +64 -64
  155. package/docs/templates/product-line/test-plan.md +7 -7
  156. package/docs/templates/production-readiness-checklist.md +57 -57
  157. package/docs/templates/progress-log.md +17 -17
  158. package/docs/templates/project-start-checklist.md +9 -9
  159. package/docs/templates/qa-report.md +48 -48
  160. package/docs/templates/sprint-contract.md +29 -29
  161. package/docs/templates/worker-dogfood-evidence.md +80 -80
  162. package/docs/templates/worker-dogfood-setup.md +68 -68
  163. package/docs/verification-matrix.md +70 -70
  164. package/examples/decision-gate-agent-dag.json +177 -177
  165. package/examples/example-dag.json +46 -46
  166. package/examples/hybrid-loop-agent-dag.json +189 -189
  167. package/harness.json +66 -66
  168. package/package.json +52 -88
  169. package/scripts/check-product-line-docs.sh +29 -29
  170. package/scripts/check-task-pool-root.sh +32 -32
  171. package/scripts/kb-bootstrap-init-skeleton.sh +240 -240
  172. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  173. package/scripts/kb-graph-incremental-prepare.sh +5 -5
  174. package/scripts/kb-graph-materialize.mjs +105 -105
  175. package/scripts/kb-graph-materialize.sh +4 -4
  176. package/scripts/kb-graph-promote.mjs +164 -164
  177. package/scripts/kb-graph-promote.sh +4 -4
  178. package/scripts/kb-query.mjs +554 -554
  179. package/scripts/kb-query.sh +5 -5
  180. package/skills/agent-worker/SKILL.md +39 -39
  181. package/skills/agent-worker/references/agent-worker-operator.md +60 -60
  182. package/skills/ai-engineering-context/SKILL.md +48 -48
  183. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  184. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  185. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  186. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  187. package/skills/analyze-product-dependencies/references/example.md +76 -76
  188. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  189. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  190. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  191. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  192. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  193. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  194. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  195. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  196. package/skills/analyze-product-requirements/SKILL.md +90 -90
  197. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  198. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  199. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  200. package/skills/analyze-product-requirements/references/example.md +86 -86
  201. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  202. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  203. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  204. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  205. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  206. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  207. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  208. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  209. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  210. package/skills/browser-tools/SKILL.md +196 -0
  211. package/skills/browser-tools/browser-content.js +103 -0
  212. package/skills/browser-tools/browser-cookies.js +35 -0
  213. package/skills/browser-tools/browser-eval.js +53 -0
  214. package/skills/browser-tools/browser-hn-scraper.js +108 -0
  215. package/skills/browser-tools/browser-nav.js +44 -0
  216. package/skills/browser-tools/browser-pick.js +162 -0
  217. package/skills/browser-tools/browser-screenshot.js +34 -0
  218. package/skills/browser-tools/browser-start.js +86 -0
  219. package/skills/browser-tools/package-lock.json +2556 -0
  220. package/skills/browser-tools/package.json +19 -0
  221. package/skills/code-review-core/SKILL.md +20 -20
  222. package/skills/codebase-scout/SKILL.md +19 -19
  223. package/skills/frontend-design-review/SKILL.md +66 -66
  224. package/skills/frontend-design-review/references/review-checklist.md +58 -58
  225. package/skills/frontend-implementation/SKILL.md +49 -47
  226. package/skills/frontend-implementation/references/code-standards.md +32 -32
  227. package/skills/frontend-implementation/references/design-spec.md +46 -46
  228. package/skills/frontend-implementation/references/node-contracts.md +27 -76
  229. package/skills/frontend-review/SKILL.md +59 -59
  230. package/skills/frontend-review/references/review-findings.md +47 -47
  231. package/skills/frontend-verification/SKILL.md +53 -53
  232. package/skills/frontend-verification/references/verification-checklist.md +68 -68
  233. package/skills/grill-me/SKILL.md +10 -10
  234. package/skills/grill-with-docs/SKILL.md +88 -88
  235. package/skills/grill-with-docs/adr-format.md +47 -47
  236. package/skills/grill-with-docs/context-format.md +60 -60
  237. package/skills/init-capability-evolution/SKILL.md +70 -70
  238. package/skills/loop-agent/SKILL.md +151 -151
  239. package/skills/loop-agent/references/README.md +67 -67
  240. package/skills/loop-agent/references/command-reference.md +527 -505
  241. package/skills/loop-agent/references/docs-converge.md +126 -126
  242. package/skills/loop-agent/references/harness-policy.md +263 -263
  243. package/skills/loop-agent/references/hybrid-dag.md +243 -238
  244. package/skills/loop-agent/references/learned/README.md +21 -21
  245. package/skills/loop-agent/references/long-running-loop.md +57 -57
  246. package/skills/loop-agent/references/model-routing.md +36 -36
  247. package/skills/loop-agent/references/multi-worktree.md +54 -54
  248. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  249. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  250. package/skills/loop-agent/references/pi-prompt.md +23 -23
  251. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  252. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  253. package/skills/loop-agent/references/task-workflow.md +89 -89
  254. package/skills/loop-agent/references/verification-and-failure-handling.md +141 -139
  255. package/skills/playwright-cli/SKILL.md +420 -420
  256. package/skills/playwright-cli/references/element-attributes.md +23 -23
  257. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  258. package/skills/playwright-cli/references/request-mocking.md +87 -87
  259. package/skills/playwright-cli/references/running-code.md +241 -241
  260. package/skills/playwright-cli/references/session-management.md +225 -225
  261. package/skills/playwright-cli/references/storage-state.md +275 -275
  262. package/skills/playwright-cli/references/test-generation.md +433 -433
  263. package/skills/playwright-cli/references/tracing.md +139 -139
  264. package/skills/playwright-cli/references/video-recording.md +143 -143
  265. package/skills/playwright-cli-case-generator/SKILL.md +74 -74
  266. package/skills/requesting-code-review/SKILL.md +101 -101
  267. package/skills/requesting-code-review/code-reviewer.md +168 -168
  268. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  269. package/skills/systematic-debugging/SKILL.md +296 -296
  270. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  271. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  272. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  273. package/skills/systematic-debugging/find-polluter.sh +63 -63
  274. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  275. package/skills/systematic-debugging/test-academic.md +14 -14
  276. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  277. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  278. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  279. package/skills/test-driven-development/SKILL.md +20 -20
  280. package/skills/using-git-worktrees/SKILL.md +215 -215
  281. package/skills/verification-before-completion/SKILL.md +154 -154
  282. package/skills/webapp-testing/SKILL.md +19 -19
@@ -1,156 +1,156 @@
1
- import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
3
-
4
- export function loadMarkdown(fileArg, label = "artifact") {
5
- const path = resolve(fileArg);
6
- try {
7
- return { path, text: readFileSync(path, "utf8") };
8
- } catch (error) {
9
- throw new Error(`Cannot read ${label} ${path}: ${error.message}`);
10
- }
11
- }
12
-
13
- export function frontmatter(text) {
14
- return text.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? "";
15
- }
16
-
17
- export function metadata(text, name) {
18
- const block = frontmatter(text);
19
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20
- return block.match(new RegExp(`^${escaped}:[ \\t]*["']?([^"'\\r\\n]+)["']?[ \\t]*$`, "m"))?.[1].trim();
21
- }
22
-
23
- export function headings(text, level) {
24
- const prefix = "#".repeat(level);
25
- const matches = [...text.matchAll(new RegExp(`^${prefix}\\s+(.+)$`, "gm"))];
26
- return matches.map((match, index) => ({
27
- title: match[1].trim(),
28
- start: match.index,
29
- text: text.slice(match.index, matches[index + 1]?.index ?? text.length),
30
- }));
31
- }
32
-
33
- export function canonical(title) {
34
- return title.replace(/^\d+(?:\.\d+)*[.、]?[ \t]*/, "").trim();
35
- }
36
-
37
- export function section(text, level, title) {
38
- return headings(text, level).find((item) => canonical(item.title) === title)?.text;
39
- }
40
-
41
- export function sections(text, level, title) {
42
- return headings(text, level).filter((item) => canonical(item.title) === title);
43
- }
44
-
45
- export function hasSubstance(block) {
46
- if (!block) return false;
47
- return block.split(/\r?\n/).slice(1).some((line) => line.trim() && !/^\|?[-:| ]+\|?$/.test(line.trim()));
48
- }
49
-
50
- export function field(block, name) {
51
- const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
52
- return block.match(new RegExp(`^-[ \\t]*${escaped}[::][ \\t]*(.+)$`, "m"))?.[1].trim().replace(/[。.]$/, "");
53
- }
54
-
55
- export function storyBlocks(block, prefix) {
56
- if (!block) return [];
57
- const matches = [...block.matchAll(new RegExp(`^###\\s+(${prefix}-\\d{3,})\\s+(.+)$`, "gm"))];
58
- return matches.map((match, index) => ({
59
- id: match[1],
60
- title: match[2].trim(),
61
- text: block.slice(match.index, matches[index + 1]?.index ?? block.length),
62
- }));
63
- }
64
-
65
- export function outputSpecBlocks(block, prefix) {
66
- return storyBlocks(block, prefix);
67
- }
68
-
69
- export function validateStorySpecCoverage(stories, specs, errors, label) {
70
- const storyIds = stories.map((item) => item.id);
71
- const specIds = specs.map((item) => item.id);
72
- for (const id of new Set(storyIds)) if (storyIds.filter((value) => value === id).length > 1) errors.push(`Duplicate story ID: ${id}.`);
73
- for (const id of new Set(specIds)) if (specIds.filter((value) => value === id).length > 1) errors.push(`Duplicate output specification: ${id}.`);
74
- for (const id of storyIds) if (!specIds.includes(id)) errors.push(`${label} is missing output specification for ${id}.`);
75
- for (const id of specIds) if (!storyIds.includes(id)) errors.push(`${label} contains output specification for unknown story ${id}.`);
76
- }
77
-
78
- export function acceptanceBlocks(story, prefix) {
79
- const matches = [...story.text.matchAll(new RegExp(`^####\\s+(${prefix}-\\d{3,})\\s+(.+)$`, "gm"))];
80
- return matches.map((match, index) => ({
81
- id: match[1],
82
- title: match[2].trim(),
83
- text: story.text.slice(match.index, matches[index + 1]?.index ?? story.text.length),
84
- }));
85
- }
86
-
87
- export function validateAcceptance(block, owner, errors) {
88
- const labels = ["Given", "When", "Then", "异常场景"];
89
- const positions = labels.map((label) => ({ label, index: block.search(new RegExp(`^${label}[::]`, "m")) }));
90
- for (const item of positions) if (item.index < 0) errors.push(`${owner} is missing ${item.label}.`);
91
- if (positions.some((item) => item.index < 0)) return;
92
- const counts = positions.map((item, index) => {
93
- const end = positions[index + 1]?.index ?? block.length;
94
- return (block.slice(item.index, end).match(/^-\s+\S.+$/gm) ?? []).length;
95
- });
96
- if (counts[0] < 1 || counts[1] < 1 || counts[2] < 2 || counts[3] < 1) {
97
- errors.push(`${owner} must contain at least 1 Given, 1 When, 2 Then, and 1 exception bullet.`);
98
- }
99
- if (counts.reduce((sum, count) => sum + count, 0) < 6) errors.push(`${owner} must contain at least six concrete bullets.`);
100
- if (!/(?:不得|不应|禁止|保留|恢复|重试|兜底|回滚|disabled)/.test(block)) {
101
- errors.push(`${owner} must include an observable protection or recovery result.`);
102
- }
103
- }
104
-
105
- export function containsUnresolvedBlockingPriority(block) {
106
- if (!block) return false;
107
- return block.split(/\r?\n/).slice(1).some((line) => {
108
- const plain = line.trim().replace(/^[-*+]\s*/, "").replace(/^\[[ xX]\]\s*/, "");
109
- if (!/\bP[01]\b/.test(plain)) return false;
110
- if (/^(?:不存在|没有|无)(?:任何)?(?:尚未|未)?(?:解决|确认|关闭|完成)?(?:的)?\s*P[01]\b/.test(plain)) return false;
111
- if (/^P[01]\b/.test(plain)) return true;
112
- return /(?:pending(?:-blocking)?|unresolved|未解决|未确认|待确认|未决|阻塞)/i.test(plain);
113
- });
114
- }
115
-
116
- export function assertHeadings(text, level, required, errors, owner) {
117
- for (const title of required) {
118
- const matches = sections(text, level, title);
119
- if (matches.length !== 1) errors.push(`${owner} must contain exactly one ${title} heading.`);
120
- else if (!hasSubstance(matches[0].text)) errors.push(`${owner} section ${title} must not be empty.`);
121
- }
122
- }
123
-
124
- export function printResult(label, path, errors) {
125
- if (errors.length) {
126
- console.error(`${label} validation failed (${errors.length}):`);
127
- for (const error of errors) console.error(`- ${error}`);
128
- process.exit(1);
129
- }
130
- console.log(`${label} validation passed: ${path}`);
131
- }
132
-
133
- export function validateArtifactLocation(artifact, errors, expectedName) {
134
- const requirementId = metadata(artifact.text, "requirement_id");
135
- const projectRootValue = metadata(artifact.text, "project_root");
136
- if (!requirementId || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(requirementId)) {
137
- errors.push("requirement_id must use lowercase letters, digits, and single hyphens.");
138
- return;
139
- }
140
- if (!projectRootValue) {
141
- errors.push("project_root is required.");
142
- return;
143
- }
144
- const projectRoot = resolve(dirname(artifact.path), projectRootValue);
145
- if (!existsSync(projectRoot) || !statSync(projectRoot).isDirectory()) {
146
- errors.push(`project_root does not resolve to an existing directory: ${projectRoot}`);
147
- return;
148
- }
149
- const expected = resolve(join(projectRoot, "docs", "product-analysis", requirementId));
150
- if (dirname(artifact.path) !== expected) {
151
- errors.push(`Artifact must be located under ${expected}.`);
152
- }
153
- if (expectedName && basename(artifact.path) !== expectedName) {
154
- errors.push(`Artifact filename must be ${expectedName}.`);
155
- }
156
- }
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+
4
+ export function loadMarkdown(fileArg, label = "artifact") {
5
+ const path = resolve(fileArg);
6
+ try {
7
+ return { path, text: readFileSync(path, "utf8") };
8
+ } catch (error) {
9
+ throw new Error(`Cannot read ${label} ${path}: ${error.message}`);
10
+ }
11
+ }
12
+
13
+ export function frontmatter(text) {
14
+ return text.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? "";
15
+ }
16
+
17
+ export function metadata(text, name) {
18
+ const block = frontmatter(text);
19
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20
+ return block.match(new RegExp(`^${escaped}:[ \\t]*["']?([^"'\\r\\n]+)["']?[ \\t]*$`, "m"))?.[1].trim();
21
+ }
22
+
23
+ export function headings(text, level) {
24
+ const prefix = "#".repeat(level);
25
+ const matches = [...text.matchAll(new RegExp(`^${prefix}\\s+(.+)$`, "gm"))];
26
+ return matches.map((match, index) => ({
27
+ title: match[1].trim(),
28
+ start: match.index,
29
+ text: text.slice(match.index, matches[index + 1]?.index ?? text.length),
30
+ }));
31
+ }
32
+
33
+ export function canonical(title) {
34
+ return title.replace(/^\d+(?:\.\d+)*[.、]?[ \t]*/, "").trim();
35
+ }
36
+
37
+ export function section(text, level, title) {
38
+ return headings(text, level).find((item) => canonical(item.title) === title)?.text;
39
+ }
40
+
41
+ export function sections(text, level, title) {
42
+ return headings(text, level).filter((item) => canonical(item.title) === title);
43
+ }
44
+
45
+ export function hasSubstance(block) {
46
+ if (!block) return false;
47
+ return block.split(/\r?\n/).slice(1).some((line) => line.trim() && !/^\|?[-:| ]+\|?$/.test(line.trim()));
48
+ }
49
+
50
+ export function field(block, name) {
51
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
52
+ return block.match(new RegExp(`^-[ \\t]*${escaped}[::][ \\t]*(.+)$`, "m"))?.[1].trim().replace(/[。.]$/, "");
53
+ }
54
+
55
+ export function storyBlocks(block, prefix) {
56
+ if (!block) return [];
57
+ const matches = [...block.matchAll(new RegExp(`^###\\s+(${prefix}-\\d{3,})\\s+(.+)$`, "gm"))];
58
+ return matches.map((match, index) => ({
59
+ id: match[1],
60
+ title: match[2].trim(),
61
+ text: block.slice(match.index, matches[index + 1]?.index ?? block.length),
62
+ }));
63
+ }
64
+
65
+ export function outputSpecBlocks(block, prefix) {
66
+ return storyBlocks(block, prefix);
67
+ }
68
+
69
+ export function validateStorySpecCoverage(stories, specs, errors, label) {
70
+ const storyIds = stories.map((item) => item.id);
71
+ const specIds = specs.map((item) => item.id);
72
+ for (const id of new Set(storyIds)) if (storyIds.filter((value) => value === id).length > 1) errors.push(`Duplicate story ID: ${id}.`);
73
+ for (const id of new Set(specIds)) if (specIds.filter((value) => value === id).length > 1) errors.push(`Duplicate output specification: ${id}.`);
74
+ for (const id of storyIds) if (!specIds.includes(id)) errors.push(`${label} is missing output specification for ${id}.`);
75
+ for (const id of specIds) if (!storyIds.includes(id)) errors.push(`${label} contains output specification for unknown story ${id}.`);
76
+ }
77
+
78
+ export function acceptanceBlocks(story, prefix) {
79
+ const matches = [...story.text.matchAll(new RegExp(`^####\\s+(${prefix}-\\d{3,})\\s+(.+)$`, "gm"))];
80
+ return matches.map((match, index) => ({
81
+ id: match[1],
82
+ title: match[2].trim(),
83
+ text: story.text.slice(match.index, matches[index + 1]?.index ?? story.text.length),
84
+ }));
85
+ }
86
+
87
+ export function validateAcceptance(block, owner, errors) {
88
+ const labels = ["Given", "When", "Then", "异常场景"];
89
+ const positions = labels.map((label) => ({ label, index: block.search(new RegExp(`^${label}[::]`, "m")) }));
90
+ for (const item of positions) if (item.index < 0) errors.push(`${owner} is missing ${item.label}.`);
91
+ if (positions.some((item) => item.index < 0)) return;
92
+ const counts = positions.map((item, index) => {
93
+ const end = positions[index + 1]?.index ?? block.length;
94
+ return (block.slice(item.index, end).match(/^-\s+\S.+$/gm) ?? []).length;
95
+ });
96
+ if (counts[0] < 1 || counts[1] < 1 || counts[2] < 2 || counts[3] < 1) {
97
+ errors.push(`${owner} must contain at least 1 Given, 1 When, 2 Then, and 1 exception bullet.`);
98
+ }
99
+ if (counts.reduce((sum, count) => sum + count, 0) < 6) errors.push(`${owner} must contain at least six concrete bullets.`);
100
+ if (!/(?:不得|不应|禁止|保留|恢复|重试|兜底|回滚|disabled)/.test(block)) {
101
+ errors.push(`${owner} must include an observable protection or recovery result.`);
102
+ }
103
+ }
104
+
105
+ export function containsUnresolvedBlockingPriority(block) {
106
+ if (!block) return false;
107
+ return block.split(/\r?\n/).slice(1).some((line) => {
108
+ const plain = line.trim().replace(/^[-*+]\s*/, "").replace(/^\[[ xX]\]\s*/, "");
109
+ if (!/\bP[01]\b/.test(plain)) return false;
110
+ if (/^(?:不存在|没有|无)(?:任何)?(?:尚未|未)?(?:解决|确认|关闭|完成)?(?:的)?\s*P[01]\b/.test(plain)) return false;
111
+ if (/^P[01]\b/.test(plain)) return true;
112
+ return /(?:pending(?:-blocking)?|unresolved|未解决|未确认|待确认|未决|阻塞)/i.test(plain);
113
+ });
114
+ }
115
+
116
+ export function assertHeadings(text, level, required, errors, owner) {
117
+ for (const title of required) {
118
+ const matches = sections(text, level, title);
119
+ if (matches.length !== 1) errors.push(`${owner} must contain exactly one ${title} heading.`);
120
+ else if (!hasSubstance(matches[0].text)) errors.push(`${owner} section ${title} must not be empty.`);
121
+ }
122
+ }
123
+
124
+ export function printResult(label, path, errors) {
125
+ if (errors.length) {
126
+ console.error(`${label} validation failed (${errors.length}):`);
127
+ for (const error of errors) console.error(`- ${error}`);
128
+ process.exit(1);
129
+ }
130
+ console.log(`${label} validation passed: ${path}`);
131
+ }
132
+
133
+ export function validateArtifactLocation(artifact, errors, expectedName) {
134
+ const requirementId = metadata(artifact.text, "requirement_id");
135
+ const projectRootValue = metadata(artifact.text, "project_root");
136
+ if (!requirementId || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(requirementId)) {
137
+ errors.push("requirement_id must use lowercase letters, digits, and single hyphens.");
138
+ return;
139
+ }
140
+ if (!projectRootValue) {
141
+ errors.push("project_root is required.");
142
+ return;
143
+ }
144
+ const projectRoot = resolve(dirname(artifact.path), projectRootValue);
145
+ if (!existsSync(projectRoot) || !statSync(projectRoot).isDirectory()) {
146
+ errors.push(`project_root does not resolve to an existing directory: ${projectRoot}`);
147
+ return;
148
+ }
149
+ const expected = resolve(join(projectRoot, "docs", "product-analysis", requirementId));
150
+ if (dirname(artifact.path) !== expected) {
151
+ errors.push(`Artifact must be located under ${expected}.`);
152
+ }
153
+ if (expectedName && basename(artifact.path) !== expectedName) {
154
+ errors.push(`Artifact filename must be ${expectedName}.`);
155
+ }
156
+ }
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: browser-tools
3
+ description: Interactive browser automation via Chrome DevTools Protocol. Use when you need to interact with web pages, test frontends, or when user interaction with a visible browser is required.
4
+ ---
5
+
6
+ # Browser Tools
7
+
8
+ Chrome DevTools Protocol tools for agent-assisted web automation. These tools connect to Chrome running on `:9222` with remote debugging enabled.
9
+
10
+ ## Setup
11
+
12
+ Run once before first use:
13
+
14
+ ```bash
15
+ cd {baseDir}/browser-tools
16
+ npm install
17
+ ```
18
+
19
+ ## Start Chrome
20
+
21
+ ```bash
22
+ {baseDir}/browser-start.js # Fresh profile
23
+ {baseDir}/browser-start.js --profile # Copy user's profile (cookies, logins)
24
+ ```
25
+
26
+ Launch Chrome with remote debugging on `:9222`. Use `--profile` to preserve user's authentication state.
27
+
28
+ ## Navigate
29
+
30
+ ```bash
31
+ {baseDir}/browser-nav.js https://example.com
32
+ {baseDir}/browser-nav.js https://example.com --new
33
+ ```
34
+
35
+ Navigate to URLs. Use `--new` flag to open in a new tab instead of reusing current tab.
36
+
37
+ ## Evaluate JavaScript
38
+
39
+ ```bash
40
+ {baseDir}/browser-eval.js 'document.title'
41
+ {baseDir}/browser-eval.js 'document.querySelectorAll("a").length'
42
+ ```
43
+
44
+ Execute JavaScript in the active tab. Code runs in async context. Use this to extract data, inspect page state, or perform DOM operations programmatically.
45
+
46
+ ## Screenshot
47
+
48
+ ```bash
49
+ {baseDir}/browser-screenshot.js
50
+ ```
51
+
52
+ Capture current viewport and return temporary file path. Use this to visually inspect page state or verify UI changes.
53
+
54
+ ## Pick Elements
55
+
56
+ ```bash
57
+ {baseDir}/browser-pick.js "Click the submit button"
58
+ ```
59
+
60
+ **IMPORTANT**: Use this tool when the user wants to select specific DOM elements on the page. This launches an interactive picker that lets the user click elements to select them. The user can select multiple elements (Cmd/Ctrl+Click) and press Enter when done. The tool returns CSS selectors for the selected elements.
61
+
62
+ Common use cases:
63
+ - User says "I want to click that button" → Use this tool to let them select it
64
+ - User says "extract data from these items" → Use this tool to let them select the elements
65
+ - When you need specific selectors but the page structure is complex or ambiguous
66
+
67
+ ## Cookies
68
+
69
+ ```bash
70
+ {baseDir}/browser-cookies.js
71
+ ```
72
+
73
+ Display all cookies for the current tab including domain, path, httpOnly, and secure flags. Use this to debug authentication issues or inspect session state.
74
+
75
+ ## Extract Page Content
76
+
77
+ ```bash
78
+ {baseDir}/browser-content.js https://example.com
79
+ ```
80
+
81
+ Navigate to a URL and extract readable content as markdown. Uses Mozilla Readability for article extraction and Turndown for HTML-to-markdown conversion. Works on pages with JavaScript content (waits for page to load).
82
+
83
+ ## When to Use
84
+
85
+ - Testing frontend code in a real browser
86
+ - Interacting with pages that require JavaScript
87
+ - When user needs to visually see or interact with a page
88
+ - Debugging authentication or session issues
89
+ - Scraping dynamic content that requires JS execution
90
+
91
+ ---
92
+
93
+ ## Efficiency Guide
94
+
95
+ ### DOM Inspection Over Screenshots
96
+
97
+ **Don't** take screenshots to see page state. **Do** parse the DOM directly:
98
+
99
+ ```javascript
100
+ // Get page structure
101
+ document.body.innerHTML.slice(0, 5000)
102
+
103
+ // Find interactive elements
104
+ Array.from(document.querySelectorAll('button, input, [role="button"]')).map(e => ({
105
+ id: e.id,
106
+ text: e.textContent.trim(),
107
+ class: e.className
108
+ }))
109
+ ```
110
+
111
+ ### Complex Scripts in Single Calls
112
+
113
+ Wrap everything in an IIFE to run multi-statement code:
114
+
115
+ ```javascript
116
+ (function() {
117
+ // Multiple operations
118
+ const data = document.querySelector('#target').textContent;
119
+ const buttons = document.querySelectorAll('button');
120
+
121
+ // Interactions
122
+ buttons[0].click();
123
+
124
+ // Return results
125
+ return JSON.stringify({ data, buttonCount: buttons.length });
126
+ })()
127
+ ```
128
+
129
+ ### Batch Interactions
130
+
131
+ **Don't** make separate calls for each click. **Do** batch them:
132
+
133
+ ```javascript
134
+ (function() {
135
+ const actions = ["btn1", "btn2", "btn3"];
136
+ actions.forEach(id => document.getElementById(id).click());
137
+ return "Done";
138
+ })()
139
+ ```
140
+
141
+ ### Typing/Input Sequences
142
+
143
+ ```javascript
144
+ (function() {
145
+ const text = "HELLO";
146
+ for (const char of text) {
147
+ document.getElementById("key-" + char).click();
148
+ }
149
+ document.getElementById("submit").click();
150
+ return "Submitted: " + text;
151
+ })()
152
+ ```
153
+
154
+ ### Reading App/Game State
155
+
156
+ Extract structured state in one call:
157
+
158
+ ```javascript
159
+ (function() {
160
+ const state = {
161
+ score: document.querySelector('.score')?.textContent,
162
+ status: document.querySelector('.status')?.className,
163
+ items: Array.from(document.querySelectorAll('.item')).map(el => ({
164
+ text: el.textContent,
165
+ active: el.classList.contains('active')
166
+ }))
167
+ };
168
+ return JSON.stringify(state, null, 2);
169
+ })()
170
+ ```
171
+
172
+ ### Waiting for Updates
173
+
174
+ If DOM updates after actions, add a small delay with bash:
175
+
176
+ ```bash
177
+ sleep 0.5 && {baseDir}/browser-eval.js '...'
178
+ ```
179
+
180
+ ### Investigate Before Interacting
181
+
182
+ Always start by understanding the page structure:
183
+
184
+ ```javascript
185
+ (function() {
186
+ return {
187
+ title: document.title,
188
+ forms: document.forms.length,
189
+ buttons: document.querySelectorAll('button').length,
190
+ inputs: document.querySelectorAll('input').length,
191
+ mainContent: document.body.innerHTML.slice(0, 3000)
192
+ };
193
+ })()
194
+ ```
195
+
196
+ Then target specific elements based on what you find.
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ import puppeteer from "puppeteer-core";
4
+ import { Readability } from "@mozilla/readability";
5
+ import { JSDOM } from "jsdom";
6
+ import TurndownService from "turndown";
7
+ import { gfm } from "turndown-plugin-gfm";
8
+
9
+ // Global timeout - exit if script takes too long
10
+ const TIMEOUT = 30000;
11
+ const timeoutId = setTimeout(() => {
12
+ console.error("✗ Timeout after 30s");
13
+ process.exit(1);
14
+ }, TIMEOUT).unref();
15
+
16
+ const url = process.argv[2];
17
+
18
+ if (!url) {
19
+ console.log("Usage: browser-content.js <url>");
20
+ console.log("\nExtracts readable content from a URL as markdown.");
21
+ console.log("\nExamples:");
22
+ console.log(" browser-content.js https://example.com");
23
+ console.log(" browser-content.js https://en.wikipedia.org/wiki/Rust_(programming_language)");
24
+ process.exit(1);
25
+ }
26
+
27
+ const b = await Promise.race([
28
+ puppeteer.connect({
29
+ browserURL: "http://localhost:9222",
30
+ defaultViewport: null,
31
+ }),
32
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
33
+ ]).catch((e) => {
34
+ console.error("✗ Could not connect to browser:", e.message);
35
+ console.error(" Run: browser-start.js");
36
+ process.exit(1);
37
+ });
38
+
39
+ const p = (await b.pages()).at(-1);
40
+ if (!p) {
41
+ console.error("✗ No active tab found");
42
+ process.exit(1);
43
+ }
44
+
45
+ await Promise.race([
46
+ p.goto(url, { waitUntil: "networkidle2" }),
47
+ new Promise((r) => setTimeout(r, 10000)),
48
+ ]).catch(() => {});
49
+
50
+ // Get HTML via CDP (works even with TrustedScriptURL restrictions)
51
+ const client = await p.createCDPSession();
52
+ const { root } = await client.send("DOM.getDocument", { depth: -1, pierce: true });
53
+ const { outerHTML } = await client.send("DOM.getOuterHTML", { nodeId: root.nodeId });
54
+ await client.detach();
55
+
56
+ const finalUrl = p.url();
57
+
58
+ // Extract with Readability
59
+ const doc = new JSDOM(outerHTML, { url: finalUrl });
60
+ const reader = new Readability(doc.window.document);
61
+ const article = reader.parse();
62
+
63
+ // Convert to markdown
64
+ function htmlToMarkdown(html) {
65
+ const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
66
+ turndown.use(gfm);
67
+ turndown.addRule("removeEmptyLinks", {
68
+ filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
69
+ replacement: () => "",
70
+ });
71
+ return turndown
72
+ .turndown(html)
73
+ .replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
74
+ .replace(/ +/g, " ")
75
+ .replace(/\s+,/g, ",")
76
+ .replace(/\s+\./g, ".")
77
+ .replace(/\n{3,}/g, "\n\n")
78
+ .trim();
79
+ }
80
+
81
+ let content;
82
+ if (article && article.content) {
83
+ content = htmlToMarkdown(article.content);
84
+ } else {
85
+ // Fallback
86
+ const fallbackDoc = new JSDOM(outerHTML, { url: finalUrl });
87
+ const fallbackBody = fallbackDoc.window.document;
88
+ fallbackBody.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach((el) => el.remove());
89
+ const main = fallbackBody.querySelector("main, article, [role='main'], .content, #content") || fallbackBody.body;
90
+ const fallbackHtml = main?.innerHTML || "";
91
+ if (fallbackHtml.trim().length > 100) {
92
+ content = htmlToMarkdown(fallbackHtml);
93
+ } else {
94
+ content = "(Could not extract content)";
95
+ }
96
+ }
97
+
98
+ console.log(`URL: ${finalUrl}`);
99
+ if (article?.title) console.log(`Title: ${article.title}`);
100
+ console.log("");
101
+ console.log(content);
102
+
103
+ process.exit(0);
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import puppeteer from "puppeteer-core";
4
+
5
+ const b = await Promise.race([
6
+ puppeteer.connect({
7
+ browserURL: "http://localhost:9222",
8
+ defaultViewport: null,
9
+ }),
10
+ new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000)),
11
+ ]).catch((e) => {
12
+ console.error("✗ Could not connect to browser:", e.message);
13
+ console.error(" Run: browser-start.js");
14
+ process.exit(1);
15
+ });
16
+
17
+ const p = (await b.pages()).at(-1);
18
+
19
+ if (!p) {
20
+ console.error("✗ No active tab found");
21
+ process.exit(1);
22
+ }
23
+
24
+ const cookies = await p.cookies();
25
+
26
+ for (const cookie of cookies) {
27
+ console.log(`${cookie.name}: ${cookie.value}`);
28
+ console.log(` domain: ${cookie.domain}`);
29
+ console.log(` path: ${cookie.path}`);
30
+ console.log(` httpOnly: ${cookie.httpOnly}`);
31
+ console.log(` secure: ${cookie.secure}`);
32
+ console.log("");
33
+ }
34
+
35
+ await b.disconnect();