@zhushanwen/subagent-core 0.2.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.
- package/README.md +114 -0
- package/dist/chunk-3VOERJPJ.js +22 -0
- package/dist/chunk-A4IVZWVX.js +31 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
- package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
- package/dist/execution/engine/engines/zcode/constants.js +22 -0
- package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
- package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
- package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
- package/dist/execution/engine/engines/zcode/reader.js +239 -0
- package/dist/execution/engine/paths.cjs +55 -0
- package/dist/execution/engine/paths.d.cts +8 -0
- package/dist/execution/engine/paths.d.ts +8 -0
- package/dist/execution/engine/paths.js +26 -0
- package/dist/execution/relay-env.cjs +62 -0
- package/dist/execution/relay-env.d.cts +33 -0
- package/dist/execution/relay-env.d.ts +33 -0
- package/dist/execution/relay-env.js +20 -0
- package/dist/index.cjs +1852 -0
- package/dist/index.d.cts +1184 -0
- package/dist/index.d.ts +1184 -0
- package/dist/index.js +1810 -0
- package/dist/types-BxyAidGf.d.cts +875 -0
- package/dist/types-BxyAidGf.d.ts +875 -0
- package/package.json +99 -0
- package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
- package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
- package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
- package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
- package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
- package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
- package/src/__tests__/m2-append-content-probe.test.ts +73 -0
- package/src/__tests__/manifest-store.test.ts +368 -0
- package/src/__tests__/record-store-cache.test.ts +335 -0
- package/src/__tests__/record-store-index.test.ts +560 -0
- package/src/__tests__/record-store-last-line.test.ts +129 -0
- package/src/__tests__/review-fix-loop-script.test.ts +288 -0
- package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
- package/src/__tests__/robustness-low-batch1.test.ts +286 -0
- package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
- package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
- package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
- package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
- package/src/__tests__/session-runner.test.ts +69 -0
- package/src/__tests__/smoke.test.ts +9 -0
- package/src/__tests__/stream-sink.test.ts +203 -0
- package/src/core/__tests__/host-services.test.ts +144 -0
- package/src/core/__tests__/logger.test.ts +78 -0
- package/src/core/__tests__/notify-ports.test.ts +104 -0
- package/src/core/host-services.ts +97 -0
- package/src/core/logger.ts +47 -0
- package/src/core/notify-ports.ts +126 -0
- package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
- package/src/execution/__tests__/alive-store.test.ts +147 -0
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
- package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
- package/src/execution/__tests__/config.test.ts +302 -0
- package/src/execution/__tests__/delivery-methods.test.ts +424 -0
- package/src/execution/__tests__/dialog-queue.test.ts +299 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
- package/src/execution/__tests__/execute-nesting.test.ts +323 -0
- package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
- package/src/execution/__tests__/execution-record.test.ts +1394 -0
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
- package/src/execution/__tests__/finalize-record.test.ts +367 -0
- package/src/execution/__tests__/finalized-marker.test.ts +104 -0
- package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
- package/src/execution/__tests__/host-mode.test.ts +87 -0
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
- package/src/execution/__tests__/model-resolver.test.ts +465 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/output-collector.test.ts +358 -0
- package/src/execution/__tests__/path-encoding.test.ts +104 -0
- package/src/execution/__tests__/pi-invocation.test.ts +134 -0
- package/src/execution/__tests__/record-store.test.ts +1057 -0
- package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
- package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
- package/src/execution/__tests__/relay-env.test.ts +42 -0
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/rpc-mode.test.ts +89 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
- package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
- package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
- package/src/execution/__tests__/session-file-gc.test.ts +293 -0
- package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
- package/src/execution/__tests__/spawn-args.test.ts +445 -0
- package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
- package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
- package/src/execution/__tests__/stdin-writer.test.ts +462 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
- package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +788 -0
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
- package/src/execution/__tests__/temp-prompt.test.ts +53 -0
- package/src/execution/__tests__/timeout-integration.test.ts +615 -0
- package/src/execution/__tests__/tombstone-store.test.ts +73 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/turn-limiter.test.ts +65 -0
- package/src/execution/__tests__/ui-channels.test.ts +187 -0
- package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
- package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
- package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
- package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
- package/src/execution/__tests__/worktree-manager.test.ts +633 -0
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
- package/src/execution/__tests__/worktree-registry.test.ts +199 -0
- package/src/execution/agent-registry.ts +199 -0
- package/src/execution/agent-result-mapper.ts +90 -0
- package/src/execution/alive-store.ts +119 -0
- package/src/execution/argv-mirror.ts +113 -0
- package/src/execution/best-effort.ts +38 -0
- package/src/execution/channel-registry-access.ts +144 -0
- package/src/execution/concurrency-pool.ts +116 -0
- package/src/execution/config.ts +165 -0
- package/src/execution/dialog-queue.ts +330 -0
- package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
- package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
- package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
- package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
- package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
- package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
- package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
- package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
- package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
- package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
- package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
- package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
- package/src/execution/engine/__tests__/paths.test.ts +39 -0
- package/src/execution/engine/__tests__/registry.test.ts +120 -0
- package/src/execution/engine/__tests__/routing.test.ts +231 -0
- package/src/execution/engine/common/data-dir.ts +66 -0
- package/src/execution/engine/common/errors.ts +183 -0
- package/src/execution/engine/common/event-journal.ts +254 -0
- package/src/execution/engine/common/journal-replay.ts +62 -0
- package/src/execution/engine/common/kill-chain.ts +221 -0
- package/src/execution/engine/common/nesting-guard.ts +50 -0
- package/src/execution/engine/common/persona-router.ts +108 -0
- package/src/execution/engine/common/pool-manager.ts +226 -0
- package/src/execution/engine/common/schema-emulation.ts +189 -0
- package/src/execution/engine/common/session-view-projection.ts +51 -0
- package/src/execution/engine/engine-discovery.ts +65 -0
- package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
- package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
- package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
- package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
- package/src/execution/engine/engines/pi/reader.ts +48 -0
- package/src/execution/engine/engines/pi/registration.ts +35 -0
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
- package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
- package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
- package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
- package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
- package/src/execution/engine/engines/zcode/constants.ts +43 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
- package/src/execution/engine/engines/zcode/launcher.ts +161 -0
- package/src/execution/engine/engines/zcode/parser.ts +436 -0
- package/src/execution/engine/engines/zcode/preparer.ts +363 -0
- package/src/execution/engine/engines/zcode/reader.ts +381 -0
- package/src/execution/engine/engines/zcode/registration.ts +37 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
- package/src/execution/engine/host-task-spec.ts +47 -0
- package/src/execution/engine/model-prompt.ts +158 -0
- package/src/execution/engine/paths.ts +42 -0
- package/src/execution/engine/port.ts +153 -0
- package/src/execution/engine/registry.ts +133 -0
- package/src/execution/engine/routing.ts +218 -0
- package/src/execution/engine/types.ts +309 -0
- package/src/execution/execute-options-mapper.ts +115 -0
- package/src/execution/execution-record.ts +984 -0
- package/src/execution/finalize-record.ts +254 -0
- package/src/execution/finalized-marker.ts +70 -0
- package/src/execution/get-state-handshake.ts +104 -0
- package/src/execution/host-mode.ts +56 -0
- package/src/execution/idle-gc.ts +47 -0
- package/src/execution/lifecycle-manager.ts +513 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +256 -0
- package/src/execution/model-config-service.ts +250 -0
- package/src/execution/model-resolver.ts +248 -0
- package/src/execution/notifier.ts +372 -0
- package/src/execution/notify-ledger.ts +580 -0
- package/src/execution/output-collector.ts +228 -0
- package/src/execution/path-encoding.ts +60 -0
- package/src/execution/pi-invocation.ts +120 -0
- package/src/execution/record-entry.ts +132 -0
- package/src/execution/record-store.ts +1265 -0
- package/src/execution/relay-env.ts +37 -0
- package/src/execution/session-context-resolver.ts +64 -0
- package/src/execution/session-file-gc.ts +120 -0
- package/src/execution/session-pending.ts +170 -0
- package/src/execution/session-reconstructor.ts +678 -0
- package/src/execution/session-runner.ts +1789 -0
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/spawn-event-adapter.ts +363 -0
- package/src/execution/stdin-writer.ts +198 -0
- package/src/execution/stream-sink.ts +126 -0
- package/src/execution/subagent-service.ts +2268 -0
- package/src/execution/subprocess-agent-runner.ts +317 -0
- package/src/execution/temp-prompt.ts +62 -0
- package/src/execution/tombstone-store.ts +72 -0
- package/src/execution/turn-limiter.ts +102 -0
- package/src/execution/types.ts +1023 -0
- package/src/execution/ui-channels.ts +216 -0
- package/src/execution/ui-interaction-model.ts +48 -0
- package/src/execution/ui-request-handler-factory.ts +243 -0
- package/src/execution/ui-request-observability.ts +81 -0
- package/src/execution/ui-request-queue.ts +184 -0
- package/src/execution/worktree-manager.ts +688 -0
- package/src/execution/worktree-registry.ts +279 -0
- package/src/index.ts +87 -0
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
- package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
- package/src/orchestration/__tests__/args-validator.test.ts +143 -0
- package/src/orchestration/__tests__/config-loader.test.ts +503 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
- package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
- package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
- package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
- package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
- package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
- package/src/orchestration/__tests__/script-lint.test.ts +831 -0
- package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
- package/src/orchestration/__tests__/test-mocks.ts +197 -0
- package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
- package/src/orchestration/__tests__/worker-host.test.ts +120 -0
- package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
- package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/agent-opts-resolver.ts +174 -0
- package/src/orchestration/args-validator.ts +127 -0
- package/src/orchestration/config-loader.ts +316 -0
- package/src/orchestration/error-recovery.ts +1018 -0
- package/src/orchestration/execute-agent-call.ts +256 -0
- package/src/orchestration/launcher.ts +455 -0
- package/src/orchestration/lifecycle.ts +399 -0
- package/src/orchestration/models/__tests__/budget.test.ts +307 -0
- package/src/orchestration/models/agent-call.ts +83 -0
- package/src/orchestration/models/budget.ts +118 -0
- package/src/orchestration/models/ports.ts +164 -0
- package/src/orchestration/models/run-runtime.ts +104 -0
- package/src/orchestration/models/run-spec.ts +83 -0
- package/src/orchestration/models/run-state.ts +44 -0
- package/src/orchestration/models/trace.ts +183 -0
- package/src/orchestration/models/types.ts +301 -0
- package/src/orchestration/models/workflow-run.ts +254 -0
- package/src/orchestration/models/workflow-script-registry.ts +35 -0
- package/src/orchestration/models/workflow-script.ts +117 -0
- package/src/orchestration/script-lint.ts +766 -0
- package/src/orchestration/skill-discovery.ts +135 -0
- package/src/orchestration/worker-handle.ts +115 -0
- package/src/orchestration/worker-host.ts +98 -0
- package/src/orchestration/worker-script-builder.ts +421 -0
- package/src/orchestration/workflow-files.ts +85 -0
- package/src/orchestration/workflow-script-registry-impl.ts +133 -0
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/meta-parser.test.ts +304 -0
- package/src/shared/__tests__/model-ref.test.ts +306 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
- package/src/shared/__tests__/resource-discovery.test.ts +749 -0
- package/src/shared/__tests__/resource-meta.test.ts +51 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/__tests__/timer-delay.test.ts +61 -0
- package/src/shared/agent-event.ts +13 -0
- package/src/shared/agent-ref.ts +57 -0
- package/src/shared/meta-parser.ts +261 -0
- package/src/shared/model-ref.ts +286 -0
- package/src/shared/resource-discovery.ts +835 -0
- package/src/shared/resource-meta.ts +65 -0
- package/src/shared/schema-env.ts +44 -0
- package/src/shared/schema-jsonify.ts +58 -0
- package/src/shared/timer-delay.ts +54 -0
- package/src/shared/xml-injection.ts +35 -0
- package/workflows/README.md +81 -0
- package/workflows/_shared/agent-refs.cjs +40 -0
- package/workflows/chain.js +137 -0
- package/workflows/map-reduce.js +180 -0
- package/workflows/parallel.js +154 -0
- package/workflows/review-fix-loop-utils.cjs +1542 -0
- package/workflows/review-fix-loop.js +1605 -0
- package/workflows/scatter-gather.js +184 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* script-lint — 单元测试。
|
|
3
|
+
*
|
|
4
|
+
* lintScript 是纯函数、零副作用、零 IO,直接传入源码字符串即可。
|
|
5
|
+
*
|
|
6
|
+
* 覆盖:入口检查、result.output/outputSchema/文件状态、bare async IIFE、
|
|
7
|
+
* 多错误同时存在、注释行跳过。
|
|
8
|
+
*
|
|
9
|
+
* 【差异说明】源码中实际不存在「未闭合括号」/「嵌套括号深度」检查——
|
|
10
|
+
* lintScript 是 API 误用 lint(非语法 lint),不做括号配对。
|
|
11
|
+
* 因此原任务清单的对应条目已替换为实际存在的检查项(IIFE / outputSchema 等)。
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from "vitest";
|
|
14
|
+
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { lintAgentMeta, lintScript } from "../script-lint.ts";
|
|
19
|
+
import { parseResourceMeta } from "../../shared/meta-parser.ts";
|
|
20
|
+
|
|
21
|
+
const WORKFLOWS_DIR = join(__dirname, "../../../workflows");
|
|
22
|
+
|
|
23
|
+
/** 取所有 error 级 finding。 */
|
|
24
|
+
function errors(findings: LintFinding[]): LintFinding[] {
|
|
25
|
+
return findings.filter((f) => f.severity === "error");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 取所有 warning 级 finding。 */
|
|
29
|
+
function warnings(findings: LintFinding[]): LintFinding[] {
|
|
30
|
+
return findings.filter((f) => f.severity === "warning");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── 基础验证 ──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
describe("合法脚本通过", () => {
|
|
36
|
+
it("包含 agent() 调用且无其他问题 → valid=true", () => {
|
|
37
|
+
const src = `const meta = { name: "x", description: "d" };\nawait agent({ prompt: "do something" });\n`;
|
|
38
|
+
const result = lintScript(src);
|
|
39
|
+
|
|
40
|
+
expect(result.valid).toBe(true);
|
|
41
|
+
expect(errors(result.findings)).toHaveLength(0);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("parallel() 也是合法入口", () => {
|
|
45
|
+
const src = `await parallel([\n { prompt: "a" },\n { prompt: "b" }\n]);\n`;
|
|
46
|
+
const result = lintScript(src);
|
|
47
|
+
|
|
48
|
+
expect(result.valid).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("pipeline() 也是合法入口", () => {
|
|
52
|
+
const src = `await pipeline({ prompt: "x" });\n`;
|
|
53
|
+
const result = lintScript(src);
|
|
54
|
+
|
|
55
|
+
expect(result.valid).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ── 入口检查 ──────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
describe("缺入口 — 必须含 agent/parallel/pipeline 之一", () => {
|
|
62
|
+
it("不含任何入口 → 报 error", () => {
|
|
63
|
+
const src = `const x = 1;\nconsole.log("no orchestration here");\n`;
|
|
64
|
+
const result = lintScript(src);
|
|
65
|
+
|
|
66
|
+
expect(result.valid).toBe(false);
|
|
67
|
+
const errs = errors(result.findings);
|
|
68
|
+
expect(errs).toHaveLength(1);
|
|
69
|
+
expect(errs[0].message).toMatch(/must call agent.*parallel.*pipeline/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("错误信息建议添加 agent/parallel/pipeline", () => {
|
|
73
|
+
const src = `const x = 1;\n`;
|
|
74
|
+
const result = lintScript(src);
|
|
75
|
+
|
|
76
|
+
const finding = errors(result.findings)[0];
|
|
77
|
+
expect(finding.suggestion).toMatch(/agent\(\).*parallel\(\).*pipeline\(\)/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("类似 agent 字符串但不构成调用不满足入口(agentX( ))", () => {
|
|
81
|
+
const src = `const meta = {};\nagentX("not a real agent call");\n`;
|
|
82
|
+
const result = lintScript(src);
|
|
83
|
+
|
|
84
|
+
expect(result.valid).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ── result.output 等错误字段 ───────────────────────────────────
|
|
89
|
+
|
|
90
|
+
describe("result.output / parsedOutput / content 错误访问", () => {
|
|
91
|
+
it("result.output → error", () => {
|
|
92
|
+
const src = `const out = await agent({ prompt: "x" });\nconst v = result.output;\n`;
|
|
93
|
+
const result = lintScript(src);
|
|
94
|
+
|
|
95
|
+
const errs = errors(result.findings);
|
|
96
|
+
const r = errs.find((f) => f.message.includes("result.output"));
|
|
97
|
+
expect(r).toBeDefined();
|
|
98
|
+
expect(r!.line).toBe(2);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("result.parsedOutput → error", () => {
|
|
102
|
+
const src = `await agent({ prompt: "x" });\nreturn result.parsedOutput;\n`;
|
|
103
|
+
const result = lintScript(src);
|
|
104
|
+
|
|
105
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.parsedOutput")))
|
|
106
|
+
.toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("result.content → error", () => {
|
|
110
|
+
const src = `await agent({ prompt: "x" });\nconsole.log(result.content);\n`;
|
|
111
|
+
const result = lintScript(src);
|
|
112
|
+
|
|
113
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.content")))
|
|
114
|
+
.toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("result 的三种错误字段都建议用 await agent() 直接取值", () => {
|
|
118
|
+
const src = `await agent({ prompt: "x" });\nresult.output;\nresult.parsedOutput;\nresult.content;\n`;
|
|
119
|
+
const result = lintScript(src);
|
|
120
|
+
|
|
121
|
+
for (const f of errors(result.findings)) {
|
|
122
|
+
expect(f.suggestion).toMatch(/await agent/);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── outputSchema 作为 key(agent 选项误用)─────────────────────
|
|
128
|
+
|
|
129
|
+
describe("outputSchema 作为 agent 选项 key", () => {
|
|
130
|
+
it("简写 outputSchema 作为 key → error", () => {
|
|
131
|
+
const src = `await agent({\n prompt: "x",\n outputSchema,\n});\n`;
|
|
132
|
+
const result = lintScript(src);
|
|
133
|
+
|
|
134
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema"))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("显式 outputSchema: ... 作为 key → error", () => {
|
|
138
|
+
const src = `await agent({\n prompt: "x",\n outputSchema: foo,\n});\n`;
|
|
139
|
+
const result = lintScript(src);
|
|
140
|
+
|
|
141
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema"))).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("schema: outputSchema 作为 value → 不报 error", () => {
|
|
145
|
+
// outputSchema 出现在 value 位置是合法的(schema 才是正确 key)
|
|
146
|
+
const src = `await agent({\n prompt: "x",\n schema: outputSchema,\n});\n`;
|
|
147
|
+
const result = lintScript(src);
|
|
148
|
+
|
|
149
|
+
expect(errors(result.findings).some((f) => f.message.includes("outputSchema")))
|
|
150
|
+
.toBe(false);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── 文件状态(warning)─────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
describe("文件传状态 / unlinkSync — warning", () => {
|
|
157
|
+
it("readFileSync 读 STATE 文件 → warning", () => {
|
|
158
|
+
const src = `await agent({ prompt: "x" });\nconst s = readFileSync(STATE_PATH, "utf-8");\n`;
|
|
159
|
+
const result = lintScript(src);
|
|
160
|
+
|
|
161
|
+
const ws = warnings(result.findings);
|
|
162
|
+
// 实际 message 是 "Reading a state file between agent calls is fragile..."(不含字面 "readFileSync")
|
|
163
|
+
expect(ws.some((w) => /state file.*fragile/i.test(w.message))).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("unlinkSync 清理 state → warning", () => {
|
|
167
|
+
const src = `await agent({ prompt: "x" });\nunlinkSync(stateFile);\n`;
|
|
168
|
+
const result = lintScript(src);
|
|
169
|
+
|
|
170
|
+
const ws = warnings(result.findings);
|
|
171
|
+
expect(ws.some((w) => w.message.includes("unlinkSync"))).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("warning 不影响 valid 判定(valid 只看 error)", () => {
|
|
175
|
+
const src = `await agent({ prompt: "x" });\nunlinkSync(stateFile);\n`;
|
|
176
|
+
const result = lintScript(src);
|
|
177
|
+
|
|
178
|
+
expect(result.valid).toBe(true);
|
|
179
|
+
expect(warnings(result.findings).length).toBeGreaterThan(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ── bare async IIFE ───────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
describe("顶层未 await 的异步 IIFE + 内部调 agent — 子进程被提前 kill", () => {
|
|
186
|
+
it("孤立 fire-and-forget IIFE 内调 agent → error", () => {
|
|
187
|
+
const src = [
|
|
188
|
+
`(async function main() {`,
|
|
189
|
+
` await agent({ prompt: "x" });`,
|
|
190
|
+
`})();`,
|
|
191
|
+
``,
|
|
192
|
+
].join("\n");
|
|
193
|
+
const result = lintScript(src);
|
|
194
|
+
|
|
195
|
+
const errs = errors(result.findings);
|
|
196
|
+
const iifeFinding = errs.find((f) => /fire-and-forget/i.test(f.message));
|
|
197
|
+
expect(iifeFinding).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("await 前缀的 IIFE 内调 agent → 不报 error(warning 或无)", () => {
|
|
201
|
+
const src = [
|
|
202
|
+
`await (async () => {`,
|
|
203
|
+
` await agent({ prompt: "x" });`,
|
|
204
|
+
`})();`,
|
|
205
|
+
``,
|
|
206
|
+
].join("\n");
|
|
207
|
+
const result = lintScript(src);
|
|
208
|
+
|
|
209
|
+
expect(errors(result.findings).some((f) => /fire-and-forget/i.test(f.message)))
|
|
210
|
+
.toBe(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("IIFE 被赋值/返回接住 → warning(非 error)", () => {
|
|
214
|
+
const src = [
|
|
215
|
+
`const p = (async () => {`,
|
|
216
|
+
` await agent({ prompt: "x" });`,
|
|
217
|
+
`})();`,
|
|
218
|
+
`await p;`,
|
|
219
|
+
``,
|
|
220
|
+
].join("\n");
|
|
221
|
+
const result = lintScript(src);
|
|
222
|
+
|
|
223
|
+
expect(errors(result.findings).some((f) => /fire-and-forget/i.test(f.message)))
|
|
224
|
+
.toBe(false);
|
|
225
|
+
expect(warnings(result.findings).some((f) => /assigned|returned/i.test(f.message)))
|
|
226
|
+
.toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("IIFE 内不含 agent/parallel/pipeline → 不报(合法的纯 I/O IIFE)", () => {
|
|
230
|
+
const src = [
|
|
231
|
+
`(async () => {`,
|
|
232
|
+
` await new Promise(r => setTimeout(r, 100));`,
|
|
233
|
+
`})();`,
|
|
234
|
+
``,
|
|
235
|
+
].join("\n");
|
|
236
|
+
const result = lintScript(src);
|
|
237
|
+
|
|
238
|
+
// 无 entry point → 入口检查报 error;但不应有 IIFE error
|
|
239
|
+
const iifeErrors = errors(result.findings).filter((f) => /iife|fire-and-forget/i.test(f.message));
|
|
240
|
+
expect(iifeErrors).toHaveLength(0);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ── 注释/字符串内的模式不被计入 ──────────────────────────────
|
|
245
|
+
|
|
246
|
+
describe("注释行内的模式跳过", () => {
|
|
247
|
+
it("// 注释中的 result.output 不报", () => {
|
|
248
|
+
const src = [
|
|
249
|
+
`await agent({ prompt: "x" });`,
|
|
250
|
+
`// here we discuss result.output as a concept`,
|
|
251
|
+
``,
|
|
252
|
+
].join("\n");
|
|
253
|
+
const result = lintScript(src);
|
|
254
|
+
|
|
255
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.output")))
|
|
256
|
+
.toBe(false);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("/* 块注释行内的 result.output 不报", () => {
|
|
260
|
+
const src = [
|
|
261
|
+
`await agent({ prompt: "x" });`,
|
|
262
|
+
`/* block comment mentions result.output on purpose */`,
|
|
263
|
+
``,
|
|
264
|
+
].join("\n");
|
|
265
|
+
const result = lintScript(src);
|
|
266
|
+
|
|
267
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.output")))
|
|
268
|
+
.toBe(false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("* 续行注释中的 result.parsedOutput 不报", () => {
|
|
272
|
+
const src = [
|
|
273
|
+
`await agent({ prompt: "x" });`,
|
|
274
|
+
`/**`,
|
|
275
|
+
` * result.parsedOutput is intentionally mentioned here`,
|
|
276
|
+
` */`,
|
|
277
|
+
``,
|
|
278
|
+
].join("\n");
|
|
279
|
+
const result = lintScript(src);
|
|
280
|
+
|
|
281
|
+
expect(errors(result.findings).some((f) => f.message.includes("result.parsedOutput")))
|
|
282
|
+
.toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("注释中的 readFileSync(STATE) 不报 warning", () => {
|
|
286
|
+
const src = [
|
|
287
|
+
`await agent({ prompt: "x" });`,
|
|
288
|
+
`// we used to readFileSync(STATE) but no longer`,
|
|
289
|
+
``,
|
|
290
|
+
].join("\n");
|
|
291
|
+
const result = lintScript(src);
|
|
292
|
+
|
|
293
|
+
expect(warnings(result.findings).some((w) => /state file/i.test(w.message)))
|
|
294
|
+
.toBe(false);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── 多错误同时存在 ────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
describe("多种错误同时存在", () => {
|
|
301
|
+
it("缺入口 + result.output + readFileSync 一起报", () => {
|
|
302
|
+
const src = [
|
|
303
|
+
`const x = result.output;`,
|
|
304
|
+
`const s = readFileSync(STATE, "utf-8");`,
|
|
305
|
+
`console.log(x, s);`,
|
|
306
|
+
``,
|
|
307
|
+
].join("\n");
|
|
308
|
+
const result = lintScript(src);
|
|
309
|
+
|
|
310
|
+
const msgs = result.findings.map((f) => f.message).join("\n");
|
|
311
|
+
expect(result.valid).toBe(false);
|
|
312
|
+
expect(msgs).toMatch(/must call agent.*parallel.*pipeline/);
|
|
313
|
+
expect(msgs).toMatch(/result\.output/);
|
|
314
|
+
// readFileSync(STATE) 触发的 warning 文案是 "Reading a state file..."
|
|
315
|
+
expect(msgs).toMatch(/state file/i);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("result.output + result.content + outputSchema 三种错误同报", () => {
|
|
319
|
+
const src = [
|
|
320
|
+
`await agent({`,
|
|
321
|
+
` prompt: "x",`,
|
|
322
|
+
` outputSchema,`,
|
|
323
|
+
`});`,
|
|
324
|
+
`const a = result.output;`,
|
|
325
|
+
`const b = result.content;`,
|
|
326
|
+
``,
|
|
327
|
+
].join("\n");
|
|
328
|
+
const result = lintScript(src);
|
|
329
|
+
|
|
330
|
+
const msgs = result.findings.map((f) => f.message).join("\n");
|
|
331
|
+
expect(msgs).toMatch(/result\.output/);
|
|
332
|
+
expect(msgs).toMatch(/result\.content/);
|
|
333
|
+
expect(msgs).toMatch(/outputSchema/);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("findings 按行号升序排列(稳定输出)", () => {
|
|
337
|
+
const src = [
|
|
338
|
+
`await agent({ prompt: "x" });`, // line 1
|
|
339
|
+
`const a = result.content;`, // line 2 → 应在 outputSchema(line 5) 之前
|
|
340
|
+
`const b = result.output;`, // line 3
|
|
341
|
+
`await agent({`,
|
|
342
|
+
` prompt: "y",`,
|
|
343
|
+
` outputSchema,`, // line 6
|
|
344
|
+
`});`,
|
|
345
|
+
``,
|
|
346
|
+
].join("\n");
|
|
347
|
+
const result = lintScript(src);
|
|
348
|
+
|
|
349
|
+
const lines = result.findings.map((f) => f.line);
|
|
350
|
+
const sorted = [...lines].sort((a, b) => a - b);
|
|
351
|
+
expect(lines).toEqual(sorted);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// ── agent description / meta.phases / phase 一致性(新增 warning 检查)─
|
|
356
|
+
|
|
357
|
+
describe("agent() 缺 description/label — warning", () => {
|
|
358
|
+
it("agent 无 description/label → 1 warning", () => {
|
|
359
|
+
const src = `await agent({ prompt: "x" });\n`;
|
|
360
|
+
const result = lintScript(src);
|
|
361
|
+
|
|
362
|
+
const descWarnings = warnings(result.findings).filter((w) =>
|
|
363
|
+
/description.*unnamed/i.test(w.message));
|
|
364
|
+
expect(descWarnings).toHaveLength(1);
|
|
365
|
+
expect(descWarnings[0].line).toBe(1);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("agent 有 description → 0 此类 warning", () => {
|
|
369
|
+
const src = `await agent({ prompt: "x", description: "review-diff" });\n`;
|
|
370
|
+
const result = lintScript(src);
|
|
371
|
+
|
|
372
|
+
expect(warnings(result.findings).some((w) => /description.*unnamed/i.test(w.message)))
|
|
373
|
+
.toBe(false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("agent 有 label(description 别名)→ 0 此类 warning", () => {
|
|
377
|
+
const src = `await agent({ prompt: "x", label: "review-diff" });\n`;
|
|
378
|
+
const result = lintScript(src);
|
|
379
|
+
|
|
380
|
+
expect(warnings(result.findings).some((w) => /description.*unnamed/i.test(w.message)))
|
|
381
|
+
.toBe(false);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// ── MF-4 重构核心回归:字符串剔除 / 非字面量实参跳过 ──────────────
|
|
386
|
+
//
|
|
387
|
+
// review-fix-loop L281 误报根因:字符串字面量里的 "agent(s)" 被 `\bagent\s*\(` 命中,
|
|
388
|
+
// 误开 agent 调用范围;三连误报根因:agent(callVar) 非字面量实参在调用点静态不可见。
|
|
389
|
+
// forEachAgentCallRange 对两者都有专门分支(stripStringsAndComments / argTail 非 `{` 跳过),
|
|
390
|
+
// 下面用例锁定这些新增逻辑(MF-3)。
|
|
391
|
+
|
|
392
|
+
describe("MF-4 回归:字符串字面量里的 agent(...) 不误触发", () => {
|
|
393
|
+
it("`const s = \"agent(s)\";` 字符串不触发 description 检测(review-fix-loop L281 回归)", () => {
|
|
394
|
+
const src = [
|
|
395
|
+
`const s = "agent(s)";`,
|
|
396
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
397
|
+
``,
|
|
398
|
+
].join("\n");
|
|
399
|
+
const result = lintScript(src);
|
|
400
|
+
|
|
401
|
+
// 字符串不产生幻影调用范围:真实调用只有 1 个且有 description → 0 warning
|
|
402
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
403
|
+
.toHaveLength(0);
|
|
404
|
+
// 也不产生 outputSchema 等连带误报
|
|
405
|
+
expect(errors(result.findings)).toHaveLength(0);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("字符串字面量里含完整调用形态 agent({...}) 同样不误触发(剔除逻辑真正生效的用例)", () => {
|
|
409
|
+
// 若 stripStringsAndComments 失效,此串会被当成字面量调用(argTail 以 { 开头)
|
|
410
|
+
// 并因缺 description 报 warning——本用例即失败
|
|
411
|
+
const src = [
|
|
412
|
+
`const s = "agent({ prompt: 'x' })";`,
|
|
413
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
414
|
+
``,
|
|
415
|
+
].join("\n");
|
|
416
|
+
const result = lintScript(src);
|
|
417
|
+
|
|
418
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
419
|
+
.toHaveLength(0);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("模板串内 agent({...}) 完整调用形态不误触发(反引号剔除路径)", () => {
|
|
423
|
+
// MF-4 既有用例只覆盖双引号字面量;workflow 脚本 prompt 常用模板串(含 ${} 插值)。
|
|
424
|
+
// 若 `...` 反引号剔除失效,模板串里的 agent({...}) 会开幻影调用范围并误报缺 description
|
|
425
|
+
const src = [
|
|
426
|
+
"const prompt = `agent({ prompt: '${x}' })`;",
|
|
427
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
428
|
+
``,
|
|
429
|
+
].join("\n");
|
|
430
|
+
const result = lintScript(src);
|
|
431
|
+
|
|
432
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
433
|
+
.toHaveLength(0);
|
|
434
|
+
expect(errors(result.findings)).toHaveLength(0);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("块注释内 agent({...}) 完整调用形态不误触发(/* */ 剔除路径)", () => {
|
|
438
|
+
// 行首 /* 注释由行级跳过兜底;行尾块注释(非行首前缀)走 stripStringsAndComments 的
|
|
439
|
+
// /* */ 剔除路径——若失效,注释里的 agent({...}) 会开幻影调用范围并误报缺 description
|
|
440
|
+
const src = [
|
|
441
|
+
`/* agent({ prompt: 'x' }) */`,
|
|
442
|
+
`const s = "x"; /* agent({ prompt: 'y' }) */`,
|
|
443
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
444
|
+
``,
|
|
445
|
+
].join("\n");
|
|
446
|
+
const result = lintScript(src);
|
|
447
|
+
|
|
448
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
449
|
+
.toHaveLength(0);
|
|
450
|
+
expect(errors(result.findings)).toHaveLength(0);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
describe("MF-4 回归:非字面量实参 agent(callVar) / agent(expr()) 跳过", () => {
|
|
455
|
+
it("agent(callVar) 不产生 description warning,且不误伤后续真实 agent 调用", () => {
|
|
456
|
+
const src = [
|
|
457
|
+
`const call = { prompt: "x" };`,
|
|
458
|
+
`await agent(call);`,
|
|
459
|
+
`await agent({ prompt: "y", description: "d" });`,
|
|
460
|
+
``,
|
|
461
|
+
].join("\n");
|
|
462
|
+
const result = lintScript(src);
|
|
463
|
+
|
|
464
|
+
// 三连误报根因:非字面量实参被当成缺 description 报 warning
|
|
465
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
466
|
+
.toHaveLength(0);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("agent(expr()) 表达式实参同样跳过", () => {
|
|
470
|
+
const src = [
|
|
471
|
+
`await agent(buildCall());`,
|
|
472
|
+
`await agent({ prompt: "y", description: "d" });`,
|
|
473
|
+
``,
|
|
474
|
+
].join("\n");
|
|
475
|
+
const result = lintScript(src);
|
|
476
|
+
|
|
477
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
478
|
+
.toHaveLength(0);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("非字面量实参 + 后续真实调用缺 description → 只对真实调用报 1 条", () => {
|
|
482
|
+
// 跳过逻辑不能吞掉后面真正缺 description 的调用
|
|
483
|
+
const src = [
|
|
484
|
+
`await agent(call);`,
|
|
485
|
+
`await agent({ prompt: "y" });`,
|
|
486
|
+
``,
|
|
487
|
+
].join("\n");
|
|
488
|
+
const result = lintScript(src);
|
|
489
|
+
|
|
490
|
+
const descWarnings = warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message));
|
|
491
|
+
expect(descWarnings).toHaveLength(1);
|
|
492
|
+
expect(descWarnings[0].line).toBe(2);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// ── MF-4 回归:checkAgentDescription 的两个跳过分支 ────────────────
|
|
497
|
+
|
|
498
|
+
describe("MF-4 回归:展开形态 / schema 内嵌 description", () => {
|
|
499
|
+
it("agent({ ...call, agent: ... }) 展开形态 → 0 description warning(review-fix-loop 三连误报根因修复点)", () => {
|
|
500
|
+
const src = [
|
|
501
|
+
`const call = { prompt: "x", model: "m" };`,
|
|
502
|
+
`await agent({ ...call, agent: "reviewer" });`,
|
|
503
|
+
``,
|
|
504
|
+
].join("\n");
|
|
505
|
+
const result = lintScript(src);
|
|
506
|
+
|
|
507
|
+
// 展开形态 description 来自运行时对象、调用点静态不可见——无法验证即不报
|
|
508
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
509
|
+
.toHaveLength(0);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("多行 agent 调用内 schema properties 含 description 字符串 → 仍报「无 description」warning(I-10 回归)", () => {
|
|
513
|
+
const src = [
|
|
514
|
+
`await agent({`,
|
|
515
|
+
` prompt: "x",`,
|
|
516
|
+
` schema: {`,
|
|
517
|
+
` type: "object",`,
|
|
518
|
+
` properties: {`,
|
|
519
|
+
` result: { type: "string", description: "the result" },`,
|
|
520
|
+
` },`,
|
|
521
|
+
` },`,
|
|
522
|
+
`});`,
|
|
523
|
+
``,
|
|
524
|
+
].join("\n");
|
|
525
|
+
const result = lintScript(src);
|
|
526
|
+
|
|
527
|
+
// schema 内嵌的 description 是 JSON Schema 字段说明,不是 agent 选项——
|
|
528
|
+
// 若被误判为「已提供」则漏报(无 warning),本用例锁定仍报 warning
|
|
529
|
+
const descWarnings = warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message));
|
|
530
|
+
expect(descWarnings).toHaveLength(1);
|
|
531
|
+
expect(descWarnings[0].line).toBe(1);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("schema 块剔除后,真正的 agent 选项 description 仍被识别(不误伤)", () => {
|
|
535
|
+
const src = [
|
|
536
|
+
`await agent({`,
|
|
537
|
+
` prompt: "x",`,
|
|
538
|
+
` schema: { properties: { r: { description: "d" } } },`,
|
|
539
|
+
` description: "review-diff",`,
|
|
540
|
+
`});`,
|
|
541
|
+
``,
|
|
542
|
+
].join("\n");
|
|
543
|
+
const result = lintScript(src);
|
|
544
|
+
|
|
545
|
+
expect(warnings(result.findings).filter((w) => /description.*unnamed/i.test(w.message)))
|
|
546
|
+
.toHaveLength(0);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe("meta.phases 非字符串数组 — warning", () => {
|
|
551
|
+
it("phases: [{...}] 对象数组 → warning", () => {
|
|
552
|
+
const src = [
|
|
553
|
+
`const meta = { phases: [{ title: "a" }, { title: "b" }] };`,
|
|
554
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
555
|
+
``,
|
|
556
|
+
].join("\n");
|
|
557
|
+
const result = lintScript(src);
|
|
558
|
+
|
|
559
|
+
expect(warnings(result.findings).some((w) => /meta\.phases.*string array/i.test(w.message)))
|
|
560
|
+
.toBe(true);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("phases: ['a'] 字符串数组 → 0 此类 warning", () => {
|
|
564
|
+
const src = [
|
|
565
|
+
`const meta = { phases: ["a"] };`,
|
|
566
|
+
`phase("a");`,
|
|
567
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
568
|
+
``,
|
|
569
|
+
].join("\n");
|
|
570
|
+
const result = lintScript(src);
|
|
571
|
+
|
|
572
|
+
expect(warnings(result.findings).some((w) => /meta\.phases.*string array/i.test(w.message)))
|
|
573
|
+
.toBe(false);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("多行对象数组(phases: [ 换行 { ... })→ warning(MF-5 跨行匹配)", () => {
|
|
577
|
+
const src = [
|
|
578
|
+
`const meta = { phases: [`,
|
|
579
|
+
` { title: "a" },`,
|
|
580
|
+
` { title: "b" },`,
|
|
581
|
+
`] };`,
|
|
582
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
583
|
+
``,
|
|
584
|
+
].join("\n");
|
|
585
|
+
const result = lintScript(src);
|
|
586
|
+
|
|
587
|
+
expect(warnings(result.findings).some((w) => /meta\.phases.*string array/i.test(w.message)))
|
|
588
|
+
.toBe(true);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it("多行字符串数组(phases: [ 换行 'a' ])→ 0 此类 warning", () => {
|
|
592
|
+
const src = [
|
|
593
|
+
`const meta = { phases: [`,
|
|
594
|
+
` "a",`,
|
|
595
|
+
`] };`,
|
|
596
|
+
`phase("a");`,
|
|
597
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
598
|
+
``,
|
|
599
|
+
].join("\n");
|
|
600
|
+
const result = lintScript(src);
|
|
601
|
+
|
|
602
|
+
expect(warnings(result.findings).some((w) => /meta\.phases.*string array/i.test(w.message)))
|
|
603
|
+
.toBe(false);
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe("声明 phases 与 phase() 调用一致性 — warning", () => {
|
|
608
|
+
it("声明 + 调用一致 → 0 此类 warning", () => {
|
|
609
|
+
const src = [
|
|
610
|
+
`const meta = { phases: ["review"] };`,
|
|
611
|
+
`phase("review");`,
|
|
612
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
613
|
+
``,
|
|
614
|
+
].join("\n");
|
|
615
|
+
const result = lintScript(src);
|
|
616
|
+
|
|
617
|
+
expect(warnings(result.findings).some((w) =>
|
|
618
|
+
/never set via phase|called but not in meta\.phases/i.test(w.message))).toBe(false);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("声明了但从不 phase() 调用 → warning", () => {
|
|
622
|
+
const src = [
|
|
623
|
+
`const meta = { phases: ["review", "fix"] };`,
|
|
624
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
625
|
+
``,
|
|
626
|
+
].join("\n");
|
|
627
|
+
const result = lintScript(src);
|
|
628
|
+
|
|
629
|
+
expect(warnings(result.findings).some((w) => /never set via phase/i.test(w.message)))
|
|
630
|
+
.toBe(true);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("phase() 调用了但未声明 → warning", () => {
|
|
634
|
+
const src = [
|
|
635
|
+
`const meta = { phases: ["review"] };`,
|
|
636
|
+
`phase("fix");`,
|
|
637
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
638
|
+
``,
|
|
639
|
+
].join("\n");
|
|
640
|
+
const result = lintScript(src);
|
|
641
|
+
|
|
642
|
+
expect(warnings(result.findings).some((w) => /called but not in meta\.phases/i.test(w.message)))
|
|
643
|
+
.toBe(true);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("对象数组 phases 不触发一致性 warning(由 checkMetaPhases 负责)", () => {
|
|
647
|
+
// 对象数组场景:checkPhaseConsistency 应跳过提取,不产生 never-set warning
|
|
648
|
+
const src = [
|
|
649
|
+
`const meta = { phases: [{ title: "a" }] };`,
|
|
650
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
651
|
+
``,
|
|
652
|
+
].join("\n");
|
|
653
|
+
const result = lintScript(src);
|
|
654
|
+
|
|
655
|
+
expect(warnings(result.findings).some((w) => /never set via phase/i.test(w.message)))
|
|
656
|
+
.toBe(false);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("无 phases 声明 + 无 phase() 调用 → 0 warning(skip 分支)", () => {
|
|
660
|
+
// checkPhaseConsistency 的 declared/called 都为空 → 提前 return(脚本不使用 phase 机制)。
|
|
661
|
+
// 断言用全量 warning 而非 message 正则过滤——skip 分支若产生 spurious warning 也会被捕获
|
|
662
|
+
const src = [
|
|
663
|
+
`const meta = { name: "x" };`,
|
|
664
|
+
`await agent({ prompt: "x", description: "d" });`,
|
|
665
|
+
``,
|
|
666
|
+
].join("\n");
|
|
667
|
+
const result = lintScript(src);
|
|
668
|
+
|
|
669
|
+
expect(warnings(result.findings)).toHaveLength(0);
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// ── m4 W1-W5:meta 质量(SSOT lint) ─────────────────────────
|
|
674
|
+
|
|
675
|
+
describe("m4 W1-W5: meta 质量 lint", () => {
|
|
676
|
+
const wf = (meta: string, body = 'await agent({ prompt: "x" });') =>
|
|
677
|
+
`/* @pi-meta\n${meta}\n*/\n${body}`;
|
|
678
|
+
|
|
679
|
+
it("TC5 W1: 已声明参数名 :/= 形态 → error;非参数 prose 不误报", () => {
|
|
680
|
+
// 参数名集合 = properties keys + patternProperties word 前缀
|
|
681
|
+
const bad = lintScript(
|
|
682
|
+
wf(
|
|
683
|
+
'name: w\ndescription: "targetType: git-diff 用法"\nphases: [a]\nparameters:\n type: object\n properties:\n targetType: { type: string }\n required: [targetType]',
|
|
684
|
+
),
|
|
685
|
+
);
|
|
686
|
+
expect(errors(bad.findings).some((f) => f.message.includes("targetType"))).toBe(true);
|
|
687
|
+
|
|
688
|
+
// note:/a=b 非参数 → 不误报(评审探针实测形态匹配误报面)
|
|
689
|
+
const good = lintScript(
|
|
690
|
+
wf(
|
|
691
|
+
'name: w\ndescription: "note: fix agent 与 batchN 互斥,见 a=b 说明"\nphases: [a]\nparameters:\n type: object\n properties:\n targetType: { type: string }\n required: [targetType]',
|
|
692
|
+
),
|
|
693
|
+
);
|
|
694
|
+
expect(errors(good.findings).length).toBe(0);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("TC5b W1: patternProperties word 前缀参数名(batch)也检查", () => {
|
|
698
|
+
const bad = lintScript(
|
|
699
|
+
wf(
|
|
700
|
+
'name: w\ndescription: "batch=high 模式"\nphases: [a]\nparameters:\n type: object\n patternProperties:\n "^batch\\\\d+$": { type: string }',
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
expect(errors(bad.findings).some((f) => f.message.includes("batch"))).toBe(true);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it("TC6 W2: >200 字符 → error;200 边界通过", () => {
|
|
707
|
+
const over = lintScript(
|
|
708
|
+
wf('name: w\ndescription: "' + "x".repeat(201) + '"\nphases: [a]'),
|
|
709
|
+
);
|
|
710
|
+
expect(errors(over.findings).some((f) => f.message.includes("200"))).toBe(true);
|
|
711
|
+
const at = lintScript(
|
|
712
|
+
wf('name: w\ndescription: "' + "x".repeat(200) + '"\nphases: [a]'),
|
|
713
|
+
);
|
|
714
|
+
expect(errors(at.findings).length).toBe(0);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("TC7 W3: 括号内句号/缩写剥离后不报;括号外多句报", () => {
|
|
718
|
+
// 括号内 e.g. + 句号 → 剥离后单句无 finding
|
|
719
|
+
const paren = lintScript(
|
|
720
|
+
wf('name: w\ndescription: "多批串行循环(批内并行 review,e.g. 每批 3 视角)"\nphases: [a]'),
|
|
721
|
+
);
|
|
722
|
+
expect(errors(paren.findings).length).toBe(0);
|
|
723
|
+
// 括号外两个句号 → error
|
|
724
|
+
const multi = lintScript(
|
|
725
|
+
wf('name: w\ndescription: "第一句。第二句"\nphases: [a]'),
|
|
726
|
+
);
|
|
727
|
+
expect(errors(multi.findings).some((f) => f.message.includes("单句"))).toBe(true);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it("TC8 W4: lintAgentMeta positive 四形态", () => {
|
|
731
|
+
const meta = (examples: unknown) =>
|
|
732
|
+
({ kind: "agent", name: "a", description: "d", examples }) as never;
|
|
733
|
+
// 正反各一 → 无 finding
|
|
734
|
+
expect(
|
|
735
|
+
lintAgentMeta(
|
|
736
|
+
meta([{ match: "review 代码", action: "reviewer", positive: true }, { match: "天气", action: "x", positive: false }]),
|
|
737
|
+
).length,
|
|
738
|
+
).toBe(0);
|
|
739
|
+
// 全正向 → error
|
|
740
|
+
expect(
|
|
741
|
+
lintAgentMeta(
|
|
742
|
+
meta([{ match: "a", action: "x", positive: true }, { match: "b", action: "y", positive: true }]),
|
|
743
|
+
).length,
|
|
744
|
+
).toBe(1);
|
|
745
|
+
// [] → error
|
|
746
|
+
expect(lintAgentMeta(meta([])).length).toBe(1);
|
|
747
|
+
// 缺失 → 无 finding(WQ1:未迁移 agent 不报错)
|
|
748
|
+
expect(lintAgentMeta(meta(undefined)).length).toBe(0);
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it("TC8a: W4 EXAMPLES_MAX 边界——3/4 条合法,5 条 → finding", () => {
|
|
752
|
+
const meta = (examples: unknown) =>
|
|
753
|
+
({ kind: "agent", name: "a", description: "d", examples }) as never;
|
|
754
|
+
const mk = (n: number) =>
|
|
755
|
+
Array.from({ length: n }, (_, i) => ({
|
|
756
|
+
match: `m${i}`,
|
|
757
|
+
action: "x",
|
|
758
|
+
positive: i % 2 === 0,
|
|
759
|
+
}));
|
|
760
|
+
expect(lintAgentMeta(meta(mk(3))).length).toBe(0); // 3 条合法
|
|
761
|
+
expect(lintAgentMeta(meta(mk(4))).length).toBe(0); // 4 条合法(上限)
|
|
762
|
+
expect(lintAgentMeta(meta(mk(5))).length).toBe(1); // 5 条超上限
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it("TC8b: W4 EXAMPLES_MAX——examples 超上限(5 条)→ finding", () => {
|
|
766
|
+
const meta = (examples: unknown) =>
|
|
767
|
+
({ kind: "agent", name: "a", description: "d", examples }) as never;
|
|
768
|
+
const five = [
|
|
769
|
+
{ match: "a", action: "x", positive: true },
|
|
770
|
+
{ match: "b", action: "x", positive: false },
|
|
771
|
+
{ match: "c", action: "x", positive: true },
|
|
772
|
+
{ match: "d", action: "x", positive: false },
|
|
773
|
+
{ match: "e", action: "x", positive: true },
|
|
774
|
+
];
|
|
775
|
+
const findings = lintAgentMeta(meta(five));
|
|
776
|
+
expect(findings.length).toBe(1);
|
|
777
|
+
expect(findings[0]!.message).toContain("上限");
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it("TC9: 5 内置真实 workflow 过 W1-W5(lintScript valid 无 meta 质量 error)", () => {
|
|
781
|
+
const files = ["chain", "parallel", "scatter-gather", "map-reduce", "review-fix-loop"];
|
|
782
|
+
for (const f of files) {
|
|
783
|
+
const src = readFileSync(join(WORKFLOWS_DIR, `${f}.js`), "utf-8");
|
|
784
|
+
// F11:先断言 meta 真实解析成功——防 parse 失败时 W1-W3 整体跳过真空通过
|
|
785
|
+
const meta = parseResourceMeta(src, "workflow");
|
|
786
|
+
expect(meta, `${f} meta 解析`).not.toBeNull();
|
|
787
|
+
const result = lintScript(src);
|
|
788
|
+
const qualityErrors = errors(result.findings).filter((x) => x.message.includes("meta."));
|
|
789
|
+
expect(qualityErrors, `${f} meta 质量`).toEqual([]);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("F2 回归: 参数名含未配对 [ 不崩溃(RegExp 注入)", () => {
|
|
794
|
+
const src = wf(
|
|
795
|
+
'name: w\ndescription: "x[ 参数说明"\nphases: [a]\nparameters:\n type: object\n properties:\n "x[": { type: string }\n required: ["x["]',
|
|
796
|
+
);
|
|
797
|
+
expect(() => lintScript(src)).not.toThrow();
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
it("F6 回归: 子串词缀不误报(task 不命中 subtask:)", () => {
|
|
801
|
+
const src = wf(
|
|
802
|
+
'name: w\ndescription: "subtask: 需要分解"\nphases: [a]\nparameters:\n type: object\n properties:\n task: { type: string }\n required: [task]',
|
|
803
|
+
);
|
|
804
|
+
const metaErrors = errors(lintScript(src).findings).filter((x) => x.message.includes("meta."));
|
|
805
|
+
expect(metaErrors).toEqual([]);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it("F6b 回归: 元字符参数名不误报(a.b 不命中 aXb:)", () => {
|
|
809
|
+
const src = wf(
|
|
810
|
+
'name: w\ndescription: "aXb: 任意字符"\nphases: [a]\nparameters:\n type: object\n properties:\n "a.b": { type: string }\n required: ["a.b"]',
|
|
811
|
+
);
|
|
812
|
+
const metaErrors = errors(lintScript(src).findings).filter((x) => x.message.includes("meta."));
|
|
813
|
+
expect(metaErrors).toEqual([]);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it("F7 回归: description 空串时 when/notFor 仍受检查", () => {
|
|
817
|
+
const src = wf(
|
|
818
|
+
'name: w\ndescription: ""\nwhen: "第一句。第二句"\nphases: [a]',
|
|
819
|
+
);
|
|
820
|
+
const metaErrors = errors(lintScript(src).findings).filter((x) => x.message.includes("meta."));
|
|
821
|
+
expect(metaErrors.length).toBeGreaterThan(0); // when 非单句被拦
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it("F8 回归: 括号外 e.g. 缩写不误报", () => {
|
|
825
|
+
const src = wf(
|
|
826
|
+
'name: w\ndescription: "Run when user wants review, e.g. iterative fix loop"\nphases: [a]',
|
|
827
|
+
);
|
|
828
|
+
const metaErrors = errors(lintScript(src).findings).filter((x) => x.message.includes("meta."));
|
|
829
|
+
expect(metaErrors).toEqual([]);
|
|
830
|
+
});
|
|
831
|
+
});
|