@jackwener/opencli 1.6.0 → 1.6.2
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/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/bun.lock +615 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1133 -182
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/twitter/search.js +67 -5
- package/dist/clis/twitter/search.test.js +83 -5
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/twitter/search.test.ts +88 -5
- package/src/clis/twitter/search.ts +68 -5
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoResearch Engine — Karpathy's 8-phase autonomous iteration loop.
|
|
3
|
+
*
|
|
4
|
+
* Phase 0: Precondition checks (git clean, no locks)
|
|
5
|
+
* Phase 1: Review (read scope files + log + git history)
|
|
6
|
+
* Phase 2: Ideate (select next change based on history)
|
|
7
|
+
* Phase 3: Modify (one atomic change — delegated to caller)
|
|
8
|
+
* Phase 4: Commit (git add + commit with experiment prefix)
|
|
9
|
+
* Phase 5: Verify (run verify command, extract metric)
|
|
10
|
+
* Phase 5.5: Guard (optional regression check)
|
|
11
|
+
* Phase 6: Decide (keep/discard/crash + rollback)
|
|
12
|
+
* Phase 7: Log (append TSV)
|
|
13
|
+
* Phase 8: Repeat
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from 'node:child_process';
|
|
17
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { type AutoResearchConfig, type IterationResult, type IterationStatus, extractMetric } from './config.js';
|
|
20
|
+
import { Logger } from './logger.js';
|
|
21
|
+
|
|
22
|
+
export interface EngineCallbacks {
|
|
23
|
+
/** Called at Phase 2-3: review context, ideate, and make ONE change.
|
|
24
|
+
* Return a one-sentence description of what was changed, or null to skip. */
|
|
25
|
+
modify(context: ModifyContext): Promise<string | null>;
|
|
26
|
+
|
|
27
|
+
/** Called when engine needs to report status */
|
|
28
|
+
onStatus?(msg: string): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ModifyContext {
|
|
32
|
+
iteration: number;
|
|
33
|
+
bestMetric: number;
|
|
34
|
+
currentMetric: number;
|
|
35
|
+
recentLog: IterationResult[];
|
|
36
|
+
gitLog: string;
|
|
37
|
+
scopeFiles: string[];
|
|
38
|
+
consecutiveDiscards: number;
|
|
39
|
+
stuckHint: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ROOT = join(import.meta.dirname ?? process.cwd(), '..');
|
|
43
|
+
|
|
44
|
+
function exec(cmd: string, opts?: { timeout?: number; cwd?: string }): string {
|
|
45
|
+
try {
|
|
46
|
+
return execSync(cmd, {
|
|
47
|
+
cwd: opts?.cwd ?? ROOT,
|
|
48
|
+
timeout: opts?.timeout ?? 120_000,
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
|
+
env: process.env,
|
|
52
|
+
}).trim();
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
return err.stdout?.trim() ?? err.message ?? '';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function execStrict(cmd: string, opts?: { timeout?: number }): string {
|
|
59
|
+
return execSync(cmd, {
|
|
60
|
+
cwd: ROOT,
|
|
61
|
+
timeout: opts?.timeout ?? 120_000,
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
64
|
+
env: process.env,
|
|
65
|
+
}).trim();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class Engine {
|
|
69
|
+
private config: AutoResearchConfig;
|
|
70
|
+
private logger: Logger;
|
|
71
|
+
private callbacks: EngineCallbacks;
|
|
72
|
+
private bestMetric: number = 0;
|
|
73
|
+
private currentMetric: number = 0;
|
|
74
|
+
private iteration: number = 0;
|
|
75
|
+
|
|
76
|
+
constructor(config: AutoResearchConfig, logPath: string, callbacks: EngineCallbacks) {
|
|
77
|
+
this.config = config;
|
|
78
|
+
this.logger = new Logger(logPath);
|
|
79
|
+
this.callbacks = callbacks;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private log(msg: string): void {
|
|
83
|
+
this.callbacks.onStatus?.(msg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Phase 0: Precondition checks */
|
|
87
|
+
private checkPreconditions(): void {
|
|
88
|
+
// Git repo exists
|
|
89
|
+
try { execStrict('git rev-parse --git-dir'); }
|
|
90
|
+
catch { throw new Error('Not a git repository'); }
|
|
91
|
+
|
|
92
|
+
// Clean working tree
|
|
93
|
+
const status = exec('git status --porcelain');
|
|
94
|
+
if (status) throw new Error(`Working tree not clean:\n${status}`);
|
|
95
|
+
|
|
96
|
+
// No stale locks
|
|
97
|
+
if (existsSync(join(ROOT, '.git', 'index.lock'))) {
|
|
98
|
+
throw new Error('Stale .git/index.lock found — remove it first');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Not detached HEAD
|
|
102
|
+
try { execStrict('git symbolic-ref HEAD'); }
|
|
103
|
+
catch { throw new Error('Detached HEAD — checkout a branch first'); }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Phase 5: Run verify command and extract metric */
|
|
107
|
+
private runVerify(): number | null {
|
|
108
|
+
this.log(' verify...');
|
|
109
|
+
const output = exec(this.config.verify, { timeout: 300_000 });
|
|
110
|
+
return extractMetric(output);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Phase 5.5: Run guard command */
|
|
114
|
+
private runGuard(): boolean {
|
|
115
|
+
if (!this.config.guard) return true;
|
|
116
|
+
this.log(' guard...');
|
|
117
|
+
try {
|
|
118
|
+
execStrict(this.config.guard, { timeout: 300_000 });
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Phase 4: Commit changes */
|
|
126
|
+
private commit(description: string): string | null {
|
|
127
|
+
// Stage all changes in scope (but not untracked outside scope)
|
|
128
|
+
exec('git add -A');
|
|
129
|
+
const diff = exec('git diff --cached --quiet; echo $?');
|
|
130
|
+
if (diff === '0') return null; // no changes
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
execStrict(`git commit -m "experiment(operate): ${description.replace(/"/g, '\\"')}"`);
|
|
134
|
+
return exec('git rev-parse --short HEAD');
|
|
135
|
+
} catch {
|
|
136
|
+
// Hook failure
|
|
137
|
+
exec('git reset HEAD');
|
|
138
|
+
return 'hook-blocked';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Phase 6: Rollback */
|
|
143
|
+
private safeRevert(): void {
|
|
144
|
+
try {
|
|
145
|
+
execStrict('git revert HEAD --no-edit');
|
|
146
|
+
} catch {
|
|
147
|
+
exec('git revert --abort');
|
|
148
|
+
exec('git reset --hard HEAD~1');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Get stuck hint when >5 consecutive discards */
|
|
153
|
+
private getStuckHint(discards: number): string | null {
|
|
154
|
+
if (discards < 5) return null;
|
|
155
|
+
const hints = [
|
|
156
|
+
'Re-read ALL scope files from scratch. Try a completely different approach.',
|
|
157
|
+
'Review entire results log — what worked before? Try combining successful changes.',
|
|
158
|
+
'Try the OPPOSITE of what has been failing.',
|
|
159
|
+
'Try a radical architectural change instead of incremental tweaks.',
|
|
160
|
+
'Simplify — remove complexity rather than adding it.',
|
|
161
|
+
];
|
|
162
|
+
return hints[Math.min(discards - 5, hints.length - 1)];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Run the main loop */
|
|
166
|
+
async run(): Promise<IterationResult[]> {
|
|
167
|
+
const results: IterationResult[] = [];
|
|
168
|
+
|
|
169
|
+
// Phase 0: Preconditions
|
|
170
|
+
this.log('Phase 0: Precondition checks...');
|
|
171
|
+
this.checkPreconditions();
|
|
172
|
+
|
|
173
|
+
// Initialize logger
|
|
174
|
+
this.logger.init(this.config);
|
|
175
|
+
|
|
176
|
+
// Baseline measurement
|
|
177
|
+
this.log('Measuring baseline...');
|
|
178
|
+
const baseline = this.runVerify();
|
|
179
|
+
if (baseline == null) throw new Error('Verify command returned no metric for baseline');
|
|
180
|
+
this.bestMetric = baseline;
|
|
181
|
+
this.currentMetric = baseline;
|
|
182
|
+
|
|
183
|
+
const baselineCommit = exec('git rev-parse --short HEAD');
|
|
184
|
+
const baselineResult: IterationResult = {
|
|
185
|
+
iteration: 0,
|
|
186
|
+
commit: baselineCommit,
|
|
187
|
+
metric: baseline,
|
|
188
|
+
delta: 0,
|
|
189
|
+
guard: this.config.guard ? (this.runGuard() ? 'pass' : 'fail') : '-',
|
|
190
|
+
status: 'baseline',
|
|
191
|
+
description: `initial state — ${this.config.metric} ${baseline}`,
|
|
192
|
+
};
|
|
193
|
+
this.logger.append(baselineResult);
|
|
194
|
+
results.push(baselineResult);
|
|
195
|
+
this.log(`Baseline: ${this.config.metric} = ${baseline}`);
|
|
196
|
+
|
|
197
|
+
// Main loop
|
|
198
|
+
const maxIter = this.config.iterations ?? Infinity;
|
|
199
|
+
for (this.iteration = 1; this.iteration <= maxIter; this.iteration++) {
|
|
200
|
+
this.log(`\n━━━ Iteration ${this.iteration}${maxIter < Infinity ? `/${maxIter}` : ''} ━━━`);
|
|
201
|
+
|
|
202
|
+
// Phase 1: Review
|
|
203
|
+
const gitLog = exec('git log --oneline -20');
|
|
204
|
+
const recentLog = this.logger.readLast(20);
|
|
205
|
+
const scopeFiles = this.config.scope;
|
|
206
|
+
const consecutiveDiscards = this.logger.consecutiveDiscards();
|
|
207
|
+
|
|
208
|
+
// Phase 2-3: Ideate + Modify (delegated to callback)
|
|
209
|
+
const context: ModifyContext = {
|
|
210
|
+
iteration: this.iteration,
|
|
211
|
+
bestMetric: this.bestMetric,
|
|
212
|
+
currentMetric: this.currentMetric,
|
|
213
|
+
recentLog,
|
|
214
|
+
gitLog,
|
|
215
|
+
scopeFiles,
|
|
216
|
+
consecutiveDiscards,
|
|
217
|
+
stuckHint: this.getStuckHint(consecutiveDiscards),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
let description: string | null;
|
|
221
|
+
try {
|
|
222
|
+
description = await this.callbacks.modify(context);
|
|
223
|
+
} catch (err: any) {
|
|
224
|
+
this.log(` modify error: ${err.message}`);
|
|
225
|
+
const result: IterationResult = {
|
|
226
|
+
iteration: this.iteration,
|
|
227
|
+
commit: '-',
|
|
228
|
+
metric: this.currentMetric,
|
|
229
|
+
delta: 0,
|
|
230
|
+
guard: '-',
|
|
231
|
+
status: 'crash',
|
|
232
|
+
description: `modify crashed: ${err.message?.slice(0, 80)}`,
|
|
233
|
+
};
|
|
234
|
+
this.logger.append(result);
|
|
235
|
+
results.push(result);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!description) {
|
|
240
|
+
const result: IterationResult = {
|
|
241
|
+
iteration: this.iteration,
|
|
242
|
+
commit: '-',
|
|
243
|
+
metric: this.currentMetric,
|
|
244
|
+
delta: 0,
|
|
245
|
+
guard: '-',
|
|
246
|
+
status: 'no-op',
|
|
247
|
+
description: 'no changes made',
|
|
248
|
+
};
|
|
249
|
+
this.logger.append(result);
|
|
250
|
+
results.push(result);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Phase 4: Commit
|
|
255
|
+
this.log(` commit: ${description}`);
|
|
256
|
+
const commitHash = this.commit(description);
|
|
257
|
+
if (!commitHash) {
|
|
258
|
+
const result: IterationResult = {
|
|
259
|
+
iteration: this.iteration,
|
|
260
|
+
commit: '-',
|
|
261
|
+
metric: this.currentMetric,
|
|
262
|
+
delta: 0,
|
|
263
|
+
guard: '-',
|
|
264
|
+
status: 'no-op',
|
|
265
|
+
description: `no diff after: ${description}`,
|
|
266
|
+
};
|
|
267
|
+
this.logger.append(result);
|
|
268
|
+
results.push(result);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (commitHash === 'hook-blocked') {
|
|
272
|
+
const result: IterationResult = {
|
|
273
|
+
iteration: this.iteration,
|
|
274
|
+
commit: '-',
|
|
275
|
+
metric: this.currentMetric,
|
|
276
|
+
delta: 0,
|
|
277
|
+
guard: '-',
|
|
278
|
+
status: 'hook-blocked',
|
|
279
|
+
description: `hook rejected: ${description}`,
|
|
280
|
+
};
|
|
281
|
+
this.logger.append(result);
|
|
282
|
+
results.push(result);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Phase 5: Verify
|
|
287
|
+
const metric = this.runVerify();
|
|
288
|
+
if (metric == null) {
|
|
289
|
+
this.log(' verify crashed — reverting');
|
|
290
|
+
this.safeRevert();
|
|
291
|
+
const result: IterationResult = {
|
|
292
|
+
iteration: this.iteration,
|
|
293
|
+
commit: '-',
|
|
294
|
+
metric: this.currentMetric,
|
|
295
|
+
delta: 0,
|
|
296
|
+
guard: '-',
|
|
297
|
+
status: 'crash',
|
|
298
|
+
description: `verify crashed: ${description}`,
|
|
299
|
+
};
|
|
300
|
+
this.logger.append(result);
|
|
301
|
+
results.push(result);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const improved = this.config.direction === 'higher'
|
|
306
|
+
? metric > this.bestMetric
|
|
307
|
+
: metric < this.bestMetric;
|
|
308
|
+
const delta = +(metric - this.bestMetric).toFixed(4);
|
|
309
|
+
const absDelta = Math.abs(delta);
|
|
310
|
+
const minDelta = this.config.minDelta ?? 0;
|
|
311
|
+
|
|
312
|
+
// Phase 5.5: Guard
|
|
313
|
+
let guardResult: 'pass' | 'fail' | '-' = '-';
|
|
314
|
+
if (this.config.guard && improved && absDelta >= minDelta) {
|
|
315
|
+
guardResult = this.runGuard() ? 'pass' : 'fail';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Phase 6: Decide
|
|
319
|
+
let status: IterationStatus;
|
|
320
|
+
if (improved && absDelta >= minDelta && (guardResult !== 'fail')) {
|
|
321
|
+
status = 'keep';
|
|
322
|
+
this.bestMetric = metric;
|
|
323
|
+
this.currentMetric = metric;
|
|
324
|
+
this.log(` ✓ KEEP — ${this.config.metric}: ${metric} (${delta >= 0 ? '+' : ''}${delta})`);
|
|
325
|
+
} else if (improved && guardResult === 'fail') {
|
|
326
|
+
this.log(' guard failed — reverting');
|
|
327
|
+
this.safeRevert();
|
|
328
|
+
status = 'discard';
|
|
329
|
+
this.log(` ✗ DISCARD (guard) — ${description}`);
|
|
330
|
+
} else {
|
|
331
|
+
this.safeRevert();
|
|
332
|
+
status = 'discard';
|
|
333
|
+
const reason = absDelta < minDelta ? 'below min delta' : 'no improvement';
|
|
334
|
+
this.log(` ✗ DISCARD (${reason}) — ${this.config.metric}: ${metric} (${delta >= 0 ? '+' : ''}${delta})`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const result: IterationResult = {
|
|
338
|
+
iteration: this.iteration,
|
|
339
|
+
commit: status === 'keep' ? commitHash : '-',
|
|
340
|
+
metric,
|
|
341
|
+
delta,
|
|
342
|
+
guard: guardResult,
|
|
343
|
+
status,
|
|
344
|
+
description,
|
|
345
|
+
};
|
|
346
|
+
this.logger.append(result);
|
|
347
|
+
results.push(result);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Summary
|
|
351
|
+
const keeps = results.filter(r => r.status === 'keep' || r.status === 'keep (reworked)');
|
|
352
|
+
const discards = results.filter(r => r.status === 'discard');
|
|
353
|
+
this.log(`\n${'━'.repeat(50)}`);
|
|
354
|
+
this.log(`Done: ${this.iteration - 1} iterations, ${keeps.length} kept, ${discards.length} discarded`);
|
|
355
|
+
this.log(`Final ${this.config.metric}: ${this.bestMetric} (started at ${results[0]?.metric})`);
|
|
356
|
+
|
|
357
|
+
return results;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Combined Test Suite Runner — runs browse + V2EX + Zhihu tasks.
|
|
4
|
+
* Reports combined score for AutoResearch iteration.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx tsx autoresearch/eval-all.ts # Run all
|
|
8
|
+
* npx tsx autoresearch/eval-all.ts --suite v2ex # Run one suite
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
12
|
+
import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { join, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const ROOT = join(__dirname, '..');
|
|
18
|
+
const RESULTS_DIR = join(__dirname, 'results');
|
|
19
|
+
|
|
20
|
+
interface SuiteResult {
|
|
21
|
+
name: string;
|
|
22
|
+
passed: number;
|
|
23
|
+
total: number;
|
|
24
|
+
failures: string[];
|
|
25
|
+
duration: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runSuite(name: string, script: string): SuiteResult {
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
try {
|
|
31
|
+
const output = execSync(`npx tsx ${script}`, {
|
|
32
|
+
cwd: ROOT,
|
|
33
|
+
timeout: 600_000,
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
env: process.env,
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Parse SCORE=X/Y from output
|
|
40
|
+
const scoreMatch = output.match(/SCORE=(\d+)\/(\d+)/);
|
|
41
|
+
const passed = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
|
|
42
|
+
const total = scoreMatch ? parseInt(scoreMatch[2], 10) : 0;
|
|
43
|
+
|
|
44
|
+
// Parse failures
|
|
45
|
+
const failures: string[] = [];
|
|
46
|
+
const failLines = output.match(/✗.*$/gm) || [];
|
|
47
|
+
for (const line of failLines) {
|
|
48
|
+
const m = line.match(/✗\s+(?:\[.*?\]\s+)?(\S+)/);
|
|
49
|
+
if (m) failures.push(m[1].replace(/:$/, ''));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { name, passed, total, failures, duration: Date.now() - start };
|
|
53
|
+
} catch (err: any) {
|
|
54
|
+
const output = err.stdout ?? '';
|
|
55
|
+
const scoreMatch = output.match(/SCORE=(\d+)\/(\d+)/);
|
|
56
|
+
const passed = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
|
|
57
|
+
const total = scoreMatch ? parseInt(scoreMatch[2], 10) : 0;
|
|
58
|
+
const failures: string[] = [];
|
|
59
|
+
const failLines = output.match(/✗.*$/gm) || [];
|
|
60
|
+
for (const line of failLines) {
|
|
61
|
+
const m = line.match(/✗\s+(?:\[.*?\]\s+)?(\S+)/);
|
|
62
|
+
if (m) failures.push(m[1].replace(/:$/, ''));
|
|
63
|
+
}
|
|
64
|
+
return { name, passed, total, failures, duration: Date.now() - start };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function main() {
|
|
69
|
+
const args = process.argv.slice(2);
|
|
70
|
+
const singleSuite = args.includes('--suite') ? args[args.indexOf('--suite') + 1] : null;
|
|
71
|
+
|
|
72
|
+
const suites = [
|
|
73
|
+
{ name: 'browse', script: 'autoresearch/eval-browse.ts' },
|
|
74
|
+
{ name: 'v2ex', script: 'autoresearch/eval-v2ex.ts' },
|
|
75
|
+
{ name: 'zhihu', script: 'autoresearch/eval-zhihu.ts' },
|
|
76
|
+
].filter(s => !singleSuite || s.name === singleSuite);
|
|
77
|
+
|
|
78
|
+
console.log(`\n🔬 Combined AutoResearch — ${suites.length} suites\n`);
|
|
79
|
+
|
|
80
|
+
const results: SuiteResult[] = [];
|
|
81
|
+
for (const suite of suites) {
|
|
82
|
+
console.log(` Running ${suite.name}...`);
|
|
83
|
+
const result = runSuite(suite.name, suite.script);
|
|
84
|
+
results.push(result);
|
|
85
|
+
const icon = result.passed === result.total ? '✓' : '✗';
|
|
86
|
+
console.log(` ${icon} ${result.name}: ${result.passed}/${result.total} (${Math.round(result.duration / 1000)}s)`);
|
|
87
|
+
if (result.failures.length > 0) {
|
|
88
|
+
for (const f of result.failures.slice(0, 5)) {
|
|
89
|
+
console.log(` ✗ ${f}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Summary
|
|
95
|
+
const totalPassed = results.reduce((s, r) => s + r.passed, 0);
|
|
96
|
+
const totalTasks = results.reduce((s, r) => s + r.total, 0);
|
|
97
|
+
const totalDuration = results.reduce((s, r) => s + r.duration, 0);
|
|
98
|
+
const allFailures = results.flatMap(r => r.failures.map(f => `${r.name}:${f}`));
|
|
99
|
+
|
|
100
|
+
console.log(`\n${'━'.repeat(50)}`);
|
|
101
|
+
console.log(` Combined: ${totalPassed}/${totalTasks}`);
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
console.log(` ${r.name}: ${r.passed}/${r.total}`);
|
|
104
|
+
}
|
|
105
|
+
console.log(` Time: ${Math.round(totalDuration / 60000)}min`);
|
|
106
|
+
if (allFailures.length > 0) {
|
|
107
|
+
console.log(`\n All failures:`);
|
|
108
|
+
for (const f of allFailures) console.log(` ✗ ${f}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Save result
|
|
112
|
+
mkdirSync(RESULTS_DIR, { recursive: true });
|
|
113
|
+
const existing = readdirSync(RESULTS_DIR).filter(f => f.startsWith('all-')).length;
|
|
114
|
+
const roundNum = String(existing + 1).padStart(3, '0');
|
|
115
|
+
const resultPath = join(RESULTS_DIR, `all-${roundNum}.json`);
|
|
116
|
+
writeFileSync(resultPath, JSON.stringify({
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
score: `${totalPassed}/${totalTasks}`,
|
|
119
|
+
suites: Object.fromEntries(results.map(r => [r.name, `${r.passed}/${r.total}`])),
|
|
120
|
+
failures: allFailures,
|
|
121
|
+
duration: `${Math.round(totalDuration / 60000)}min`,
|
|
122
|
+
}, null, 2), 'utf-8');
|
|
123
|
+
console.log(`\n Results saved to: ${resultPath}`);
|
|
124
|
+
console.log(`\nSCORE=${totalPassed}/${totalTasks}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
main();
|