pi-harness-runtime 1.1.440 → 1.1.441
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/harness/e2e/auto-test-loop.ts +2 -0
- package/harness/e2e/browser-scrapers/base-browser-scraper.ts +2 -0
- package/harness/e2e/browser-scrapers/chrome-profile-manager.ts +2 -0
- package/harness/e2e/browser-scrapers/codex-browser-scraper.ts +2 -0
- package/harness/e2e/tools-detector.ts +2 -0
- package/harness/key-loader.ts +2 -0
- package/harness/langchain/checkpointer.ts +2 -0
- package/harness/langchain/cron-cli.ts +2 -0
- package/harness/langchain/graph.ts +2 -0
- package/harness/notification-center.ts +2 -0
- package/harness/secret-provider.ts +2 -0
- package/index.ts +2 -0
- package/package.json +7 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/extension.d.ts.map +1 -1
- package/packages/agent-policy/dist/extension.js +2 -0
- package/packages/agent-policy/dist/extension.js.map +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/extension.ts +2 -0
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/dist/minimax-browser-auth.d.ts.map +1 -1
- package/packages/auth/dist/minimax-browser-auth.js +23 -1
- package/packages/auth/dist/minimax-browser-auth.js.map +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/auth/src/minimax-browser-auth.ts +30 -5
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/dist/inbox.d.ts.map +1 -1
- package/packages/autonomous-runtime/dist/inbox.js +2 -0
- package/packages/autonomous-runtime/dist/inbox.js.map +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/autonomous-runtime/src/inbox.ts +2 -0
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/dist/src/clipboard.d.ts.map +1 -1
- package/packages/clipboard/dist/src/clipboard.js +2 -0
- package/packages/clipboard/dist/src/clipboard.js.map +1 -1
- package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
- package/packages/clipboard/dist/src/copy-sync.js +2 -0
- package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
- package/packages/clipboard/dist/src/gist-relay.d.ts.map +1 -1
- package/packages/clipboard/dist/src/gist-relay.js +2 -0
- package/packages/clipboard/dist/src/gist-relay.js.map +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard/src/clipboard.ts +2 -0
- package/packages/clipboard/src/copy-sync.ts +2 -0
- package/packages/clipboard/src/gist-relay.ts +2 -0
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/delegation-store.js +1 -4
- package/packages/contracts/dist/workspace-env.js +28 -1
- package/packages/contracts/package.json +1 -1
- package/packages/contracts/src/delegation-store.ts +9 -4
- package/packages/contracts/src/workspace-env.ts +29 -4
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/dist/src/store.d.ts.map +1 -1
- package/packages/event-store/dist/src/store.js +2 -0
- package/packages/event-store/dist/src/store.js.map +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/event-store/src/store.ts +3 -0
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/dist/analyzer.d.ts.map +1 -1
- package/packages/generic-web-plugin/dist/analyzer.js +2 -0
- package/packages/generic-web-plugin/dist/analyzer.js.map +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/generic-web-plugin/src/analyzer.ts +3 -0
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/dist/mcp-client.d.ts.map +1 -1
- package/packages/knowledge-retrieval/dist/mcp-client.js +0 -1
- package/packages/knowledge-retrieval/dist/mcp-client.js.map +1 -1
- package/packages/knowledge-retrieval/package.json +1 -4
- package/packages/knowledge-retrieval/src/mcp-client.ts +0 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/mcp-adapter/dist/server.d.ts.map +1 -1
- package/packages/mcp-adapter/dist/server.js +2 -0
- package/packages/mcp-adapter/dist/server.js.map +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/mcp-adapter/src/server.ts +3 -0
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/dist/index.d.ts.map +1 -1
- package/packages/paperclip-integration/dist/index.js +2 -0
- package/packages/paperclip-integration/dist/index.js.map +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/paperclip-integration/src/index.ts +2 -0
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/dist/audit.d.ts.map +1 -1
- package/packages/privilege-broker/dist/audit.js +2 -0
- package/packages/privilege-broker/dist/audit.js.map +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/privilege-broker/src/audit.ts +2 -0
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/dist/compiler.d.ts.map +1 -1
- package/packages/prompt-compiler/dist/compiler.js +2 -0
- package/packages/prompt-compiler/dist/compiler.js.map +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-compiler/src/compiler.ts +2 -0
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/dist/client.d.ts.map +1 -1
- package/packages/skill-mcp-client/dist/client.js +0 -1
- package/packages/skill-mcp-client/dist/client.js.map +1 -1
- package/packages/skill-mcp-client/package.json +1 -4
- package/packages/skill-mcp-client/src/client.ts +0 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/parser.d.ts.map +1 -1
- package/packages/skills/dist/parser.js +2 -0
- package/packages/skills/dist/parser.js.map +1 -1
- package/packages/skills/dist/skill-versioning.d.ts.map +1 -1
- package/packages/skills/dist/skill-versioning.js +2 -0
- package/packages/skills/dist/skill-versioning.js.map +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/skills/src/parser.ts +3 -0
- package/packages/skills/src/skill-versioning.ts +2 -0
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/dist/index.js +0 -2
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/webmcp-tester/src/index.ts +0 -3
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/dist/trigger.d.ts.map +1 -1
- package/packages/write-review/dist/trigger.js +1 -2
- package/packages/write-review/dist/trigger.js.map +1 -1
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/trigger.ts +1 -2
- package/scripts/lib/runtime-imports.ts +189 -3
- package/scripts/telegram-webhook-server.ts +2 -0
- package/scripts/verify-package-contents.ts +148 -1
- package/harness/e2e/tools-detector.test.ts +0 -155
- package/harness/glm-quota-countdown.test.ts +0 -323
- package/harness/langchain/agents.test.ts +0 -39
- package/harness/langchain/checkpointer.test.ts +0 -243
- package/harness/langchain/daemon.test.ts +0 -459
- package/harness/langchain/model-router.test.ts +0 -191
- package/harness/langchain/ping-pong-middleware.test.ts +0 -69
- package/harness/langchain/scoreboard.test.ts +0 -267
- package/harness/langchain/status-line.test.ts +0 -106
- package/harness/langchain/surge.test.ts +0 -467
- package/harness/langchain/widget.test.ts +0 -251
- package/harness/skill-sync.test.ts +0 -57
- package/harness/ssh-detach-interceptor.test.ts +0 -263
- package/harness/ssh-hang-recovery.test.ts +0 -86
- package/packages/a2a-adapter/test/a2a.test.ts +0 -151
- package/packages/agent-policy/test/interlock.test.ts +0 -101
- package/packages/agent-policy/test/policy-retention.test.ts +0 -380
- package/packages/agent-policy/test/policy.test.ts +0 -387
- package/packages/auth/test/auth.test.ts +0 -238
- package/packages/autonomous-refactor/test/refactor.test.ts +0 -202
- package/packages/autonomous-runtime/test/types.test.ts +0 -95
- package/packages/cache-strategy/test/cache.test.ts +0 -137
- package/packages/capability-registry/test/registry.test.ts +0 -204
- package/packages/checkpoint/test/engine.test.ts +0 -265
- package/packages/cli-plugin-sdk/test/cli.test.ts +0 -45
- package/packages/code-generation/test/generator.test.ts +0 -238
- package/packages/code-review/test/engine.test.ts +0 -302
- package/packages/codex-adapter/test/provider.test.ts +0 -142
- package/packages/codex-watcher/test/watch.test.ts +0 -453
- package/packages/context-compiler/test/context-compiler.test.ts +0 -746
- package/packages/context-manager/test/context.test.ts +0 -198
- package/packages/contracts/test/delegation.test.ts +0 -350
- package/packages/contracts/test/workspace-env.test.ts +0 -802
- package/packages/cookie-sanitizer/test/infer.test.ts +0 -110
- package/packages/cookie-sanitizer/test/normalize.test.ts +0 -113
- package/packages/cookie-sanitizer/test/parse.test.ts +0 -204
- package/packages/cookie-sanitizer/test/sync.test.ts +0 -237
- package/packages/cookie-sanitizer/test/watcher.test.ts +0 -27
- package/packages/cost-optimizer/test/cost-optimizer.test.ts +0 -438
- package/packages/dependency-analyzer/test/analyzer.test.ts +0 -49
- package/packages/django-plugin/test/analyzer.test.ts +0 -195
- package/packages/doc-generator/test/docs.test.ts +0 -168
- package/packages/durable-review/test/acceptance.test.ts +0 -357
- package/packages/durable-review/test/smoke.test.ts +0 -107
- package/packages/evaluation-engine/test/engine.test.ts +0 -277
- package/packages/evaluation-runner/test/runner.test.ts +0 -168
- package/packages/event-bus/test/bus.test.ts +0 -100
- package/packages/event-store/test/store.test.ts +0 -115
- package/packages/experience-replay/test/replay.test.ts +0 -267
- package/packages/feedback-collector/test/collector.test.ts +0 -142
- package/packages/framework-detector/test/detector.test.ts +0 -75
- package/packages/framework-plugin-sdk/test/manager.test.ts +0 -320
- package/packages/frappe-plugin/test/analyzer.test.ts +0 -250
- package/packages/generic-web-plugin/test/analyzer.test.ts +0 -134
- package/packages/health-monitor/test/health.test.ts +0 -231
- package/packages/intent-analyzer/test/analyzer.test.ts +0 -72
- package/packages/intent-analyzer/test/ping-pong.test.ts +0 -96
- package/packages/jev-judge/tests/auto-continue-context.test.ts +0 -280
- package/packages/jev-judge/tests/continuation-controller.test.ts +0 -1396
- package/packages/jev-judge/tests/jev-provider-chain.test.ts +0 -536
- package/packages/jev-judge/tests/outcome-application.test.ts +0 -132
- package/packages/jev-judge/tests/question-risk.test.ts +0 -129
- package/packages/jev-judge/tests/typesafe-provider.test.ts +0 -640
- package/packages/jev-judge/tests/validation-ledger.test.ts +0 -181
- package/packages/jev-judge/tests/validation-plan.test.ts +0 -170
- package/packages/jev-judge/tests/validation-tie-breaker.test.ts +0 -88
- package/packages/laravel-plugin/test/analyzer.test.ts +0 -220
- package/packages/learning-engine/test/engine.test.ts +0 -331
- package/packages/logging/test/buffer.test.ts +0 -44
- package/packages/logging/test/classification.test.ts +0 -137
- package/packages/logging/test/config.test.ts +0 -97
- package/packages/logging/test/fanout.test.ts +0 -186
- package/packages/logging/test/identity-transport.test.ts +0 -197
- package/packages/logging/test/incidents.test.ts +0 -404
- package/packages/logging/test/ingest-schema.test.ts +0 -184
- package/packages/logging/test/integration.test.ts +0 -140
- package/packages/logging/test/log-http.test.ts +0 -246
- package/packages/logging/test/redact.test.ts +0 -109
- package/packages/logging/test/sampler.test.ts +0 -57
- package/packages/logging/test/server.test.ts +0 -444
- package/packages/logging/test/shipper.test.ts +0 -152
- package/packages/logging/test/spool.test.ts +0 -96
- package/packages/mcp-adapter/test/server.test.ts +0 -91
- package/packages/memory-engine/test/engine.test.ts +0 -456
- package/packages/model-registry/test/registry.test.ts +0 -342
- package/packages/nextjs-plugin/test/analyzer.test.ts +0 -149
- package/packages/notification/test/approval-payload.test.ts +0 -269
- package/packages/notification/test/detail-format.test.ts +0 -106
- package/packages/notification/test/notification.test.ts +0 -375
- package/packages/notification/test/pending-question-lifecycle.test.ts +0 -195
- package/packages/notification/test/pending-question.test.ts +0 -331
- package/packages/notification/test/tooling-proposal-payload.test.ts +0 -212
- package/packages/observability/test/observability.test.ts +0 -1350
- package/packages/performance-optimizer/test/optimizer.test.ts +0 -267
- package/packages/project-analyzer/test/index.test.ts +0 -1719
- package/packages/prompt-compiler/test/prompt-compiler.test.ts +0 -813
- package/packages/prompt-versioning/test/versioning.test.ts +0 -112
- package/packages/provider-adapter-sdk/test/sdk.test.ts +0 -220
- package/packages/provider-router/test/provider-router.test.ts +0 -405
- package/packages/provider-selector/test/selector.test.ts +0 -192
- package/packages/providers/test/providers.test.ts +0 -225
- package/packages/qdrant-skills/test/collection-info.test.ts +0 -165
- package/packages/qdrant-skills/test/embedder-batch.test.ts +0 -184
- package/packages/qdrant-skills/test/embedder.test.ts +0 -97
- package/packages/quota-manager/test/quota.test.ts +0 -432
- package/packages/rate-limiter/test/limiter.test.ts +0 -128
- package/packages/react-vite-plugin/test/analyzer.test.ts +0 -139
- package/packages/relay/test/client.test.ts +0 -446
- package/packages/relay/test/relay-e2e.test.ts +0 -299
- package/packages/relay/test/relay-endpoint.test.ts +0 -278
- package/packages/release-manager/test/release.test.ts +0 -215
- package/packages/requirement-compiler/test/requirement-compiler.test.ts +0 -743
- package/packages/runtime/test/runtime-api.test.ts +0 -169
- package/packages/runtime/test/runtime.test.ts +0 -297
- package/packages/scheduler/test/scheduler.test.ts +0 -125
- package/packages/session/test/manager.test.ts +0 -233
- package/packages/shared-context/test/shared-context.test.ts +0 -114
- package/packages/skill-registry/test/registry.test.ts +0 -508
- package/packages/skills/test/init-skills.test.ts +0 -114
- package/packages/skills/test/remote-skills.test.ts +0 -360
- package/packages/skills/test/remote-source.test.ts +0 -248
- package/packages/skills/test/sanitize.test.ts +0 -250
- package/packages/skills/test/vector-indexing.test.ts +0 -205
- package/packages/skills/test/vector-schema.test.ts +0 -180
- package/packages/skills/test/vector-threshold.test.ts +0 -108
- package/packages/sprint-planner/test/planner.test.ts +0 -187
- package/packages/task-compiler/test/task-compiler.test.ts +0 -628
- package/packages/test-data-generator/test/generator.test.ts +0 -265
- package/packages/test-generator/test/generator.test.ts +0 -156
- package/packages/token-estimation/test/token-estimation.test.ts +0 -207
- package/packages/token-optimizer/test/optimizer.test.ts +0 -141
- package/packages/tooling-preference/tests/changed-files.test.ts +0 -124
- package/packages/tooling-preference/tests/detect.test.ts +0 -188
- package/packages/tooling-preference/tests/proposal.test.ts +0 -107
- package/packages/tooling-preference/tests/quality-check.test.ts +0 -272
- package/packages/tooling-preference/tests/registry.test.ts +0 -51
- package/packages/tooling-preference/tests/store.test.ts +0 -99
- package/packages/trajectory/test/store.test.ts +0 -250
- package/packages/tui/test/harness-footer.test.ts +0 -559
- package/packages/tui/test/harness-header.test.ts +0 -590
- package/packages/two-phase-review/test/classify.test.ts +0 -102
- package/packages/two-phase-review/test/helpers.ts +0 -72
- package/packages/two-phase-review/test/rules.test.ts +0 -150
- package/packages/two-phase-review/test/runtime.test.ts +0 -362
- package/packages/two-phase-review/test/scope.test.ts +0 -186
- package/packages/two-phase-review/test/signals.test.ts +0 -183
- package/packages/two-phase-review/test/state.test.ts +0 -201
- package/packages/wiki/test/basic.test.ts +0 -242
- package/packages/workspace-scanner/test/scanner.test.ts +0 -43
- package/packages/worktree/test/worktree.test.ts +0 -163
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The review scope.
|
|
3
|
-
*
|
|
4
|
-
* The scope is the diff of the task, never the whole repository. A workspace
|
|
5
|
-
* without Git has no honest scope, so nothing is reviewed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from "bun:test";
|
|
9
|
-
import { execFileSync } from "node:child_process";
|
|
10
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import {
|
|
13
|
-
chunkScope,
|
|
14
|
-
collectScope,
|
|
15
|
-
countModules,
|
|
16
|
-
currentRevision,
|
|
17
|
-
isGitRepository,
|
|
18
|
-
isReviewablePath,
|
|
19
|
-
moduleKey,
|
|
20
|
-
parseNumstat,
|
|
21
|
-
shouldChunkScope,
|
|
22
|
-
} from "../src/scope.js";
|
|
23
|
-
import { withPlainTempDir, withTempDir } from "./helpers.js";
|
|
24
|
-
|
|
25
|
-
function git(root: string, ...args: string[]): void {
|
|
26
|
-
// The ambient Git configuration must not decide whether the fixture works:
|
|
27
|
-
// a global identity, a signing key or a hook would break the commit.
|
|
28
|
-
try {
|
|
29
|
-
execFileSync(
|
|
30
|
-
"git",
|
|
31
|
-
[
|
|
32
|
-
"-c",
|
|
33
|
-
"user.email=test@example.com",
|
|
34
|
-
"-c",
|
|
35
|
-
"user.name=Test",
|
|
36
|
-
"-c",
|
|
37
|
-
"commit.gpgsign=false",
|
|
38
|
-
"-c",
|
|
39
|
-
"core.hooksPath=/dev/null",
|
|
40
|
-
...args,
|
|
41
|
-
],
|
|
42
|
-
{ cwd: root, stdio: "pipe" },
|
|
43
|
-
);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const detail = error as { stderr?: Buffer; status?: number; signal?: string; message?: string };
|
|
46
|
-
const stderr = detail.stderr?.toString() ?? "";
|
|
47
|
-
throw new Error(
|
|
48
|
-
`git ${args.join(" ")} failed in ${root}: status=${detail.status} signal=${detail.signal} message=${detail.message} stderr=${stderr}`,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** A fresh Git repository with one commit. */
|
|
54
|
-
function withRepository(body: (root: string) => void): void {
|
|
55
|
-
withTempDir("scope", (root) => {
|
|
56
|
-
git(root, "init", "-q");
|
|
57
|
-
body(root);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
describe("isReviewablePath", () => {
|
|
62
|
-
it("keeps the source files", () => {
|
|
63
|
-
expect(isReviewablePath("packages/notification/src/index.ts")).toBe(true);
|
|
64
|
-
expect(isReviewablePath("index.ts")).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("drops the build output", () => {
|
|
68
|
-
expect(isReviewablePath("dist/index.js")).toBe(false);
|
|
69
|
-
expect(isReviewablePath("node_modules/x/index.js")).toBe(false);
|
|
70
|
-
expect(isReviewablePath("coverage/lcov.info")).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("drops the build output of a nested module", () => {
|
|
74
|
-
expect(isReviewablePath("packages/foo/dist/index.js")).toBe(false);
|
|
75
|
-
expect(isReviewablePath("apps/x/coverage/lcov.info")).toBe(false);
|
|
76
|
-
expect(isReviewablePath("services/x/vendor/lib.js")).toBe(false);
|
|
77
|
-
expect(isReviewablePath("packages/foo/node_modules/x/index.js")).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe("moduleKey", () => {
|
|
82
|
-
it("names the module of a file", () => {
|
|
83
|
-
expect(moduleKey("packages/notification/src/index.ts")).toBe("packages/notification");
|
|
84
|
-
expect(moduleKey("harness/wiki-integration.ts")).toBe("harness");
|
|
85
|
-
expect(moduleKey("index.ts")).toBe("root");
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe("collectScope", () => {
|
|
90
|
-
it("reads the diff since the start revision", () => {
|
|
91
|
-
withRepository((root) => {
|
|
92
|
-
mkdirSync(join(root, "packages", "notification"), { recursive: true });
|
|
93
|
-
writeFileSync(join(root, "packages", "notification", "index.ts"), "export const a = 1;\n");
|
|
94
|
-
git(root, "add", "-A");
|
|
95
|
-
git(root, "commit", "-qm", "base");
|
|
96
|
-
const start = currentRevision(root);
|
|
97
|
-
expect(start).toBeTruthy();
|
|
98
|
-
|
|
99
|
-
writeFileSync(join(root, "packages", "notification", "index.ts"), "export const a = 2;\n");
|
|
100
|
-
writeFileSync(join(root, "packages", "notification", "new.ts"), "export const b = 1;\n");
|
|
101
|
-
|
|
102
|
-
const scope = collectScope(root, start);
|
|
103
|
-
expect(scope.detected).toBe(true);
|
|
104
|
-
expect(scope.files).toContain("packages/notification/index.ts");
|
|
105
|
-
expect(scope.files).toContain("packages/notification/new.ts");
|
|
106
|
-
expect(scope.metrics.files).toBe(2);
|
|
107
|
-
expect(scope.metrics.modules).toBe(1);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("ignores the build output", () => {
|
|
112
|
-
withRepository((root) => {
|
|
113
|
-
mkdirSync(join(root, "dist"), { recursive: true });
|
|
114
|
-
writeFileSync(join(root, "dist", "index.js"), "built\n");
|
|
115
|
-
git(root, "add", "-A");
|
|
116
|
-
git(root, "commit", "-qm", "base");
|
|
117
|
-
const start = currentRevision(root);
|
|
118
|
-
writeFileSync(join(root, "dist", "index.js"), "built again\n");
|
|
119
|
-
|
|
120
|
-
const scope = collectScope(root, start);
|
|
121
|
-
expect(scope.files).not.toContain("dist/index.js");
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("reports no scope without Git", () => {
|
|
126
|
-
withPlainTempDir("plain", (plain) => {
|
|
127
|
-
expect(isGitRepository(plain)).toBe(false);
|
|
128
|
-
const scope = collectScope(plain, null);
|
|
129
|
-
expect(scope.detected).toBe(false);
|
|
130
|
-
expect(scope.files).toEqual([]);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("reports no scope when the start revision is gone", () => {
|
|
135
|
-
withRepository((root) => {
|
|
136
|
-
// A missing baseline would silently reduce the scope to the last
|
|
137
|
-
// commit, so the scope is empty instead of incomplete.
|
|
138
|
-
const scope = collectScope(root, "0000000000000000000000000000000000000000");
|
|
139
|
-
expect(scope.detected).toBe(false);
|
|
140
|
-
expect(scope.files).toEqual([]);
|
|
141
|
-
expect(scope.reason).toMatch(/does not exist/);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("parseNumstat", () => {
|
|
147
|
-
it("adds the changed lines", () => {
|
|
148
|
-
expect(parseNumstat("3\t1\tindex.ts\n0\t0\tother.ts\n")).toBe(4);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("chunkScope", () => {
|
|
153
|
-
it("splits the scope by module", () => {
|
|
154
|
-
const chunks = chunkScope([
|
|
155
|
-
"packages/notification/src/a.ts",
|
|
156
|
-
"packages/notification/src/b.ts",
|
|
157
|
-
"packages/jev-judge/src/c.ts",
|
|
158
|
-
]);
|
|
159
|
-
expect(chunks).toHaveLength(2);
|
|
160
|
-
expect(chunks[0]?.id).toBe("chunk-1");
|
|
161
|
-
expect(chunks.map((chunk) => chunk.files.length)).toEqual([2, 1]);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("splits a big module into several chunks", () => {
|
|
165
|
-
const files = Array.from({ length: 7 }, (_, index) => `packages/x/src/file-${index}.ts`);
|
|
166
|
-
const chunks = chunkScope(files, { maxFilesPerChunk: 3 });
|
|
167
|
-
expect(chunks).toHaveLength(3);
|
|
168
|
-
expect(chunks[2]?.files).toHaveLength(1);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("returns nothing for an empty scope", () => {
|
|
172
|
-
expect(chunkScope([])).toEqual([]);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("countModules and shouldChunkScope", () => {
|
|
177
|
-
it("counts the modules", () => {
|
|
178
|
-
expect(countModules(["packages/a/x.ts", "packages/b/y.ts", "packages/a/z.ts"])).toBe(2);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("measures the scope instead of assuming it", () => {
|
|
182
|
-
expect(shouldChunkScope({ files: 3, modules: 1, changedLines: 100 })).toBe(false);
|
|
183
|
-
expect(shouldChunkScope({ files: 20, modules: 1, changedLines: 100 })).toBe(true);
|
|
184
|
-
expect(shouldChunkScope({ files: 3, modules: 5, changedLines: 100 })).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Review signals.
|
|
3
|
-
*
|
|
4
|
-
* A signal is only raised for a comprehensive review. An ordinary task-level
|
|
5
|
-
* check (a type error of the file that is edited right now) stays actionable
|
|
6
|
-
* and must not be deferred.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from "bun:test";
|
|
10
|
-
import {
|
|
11
|
-
detectReviewInvocation,
|
|
12
|
-
dispatchAgentName,
|
|
13
|
-
explicitReviewRequest,
|
|
14
|
-
extractFiles,
|
|
15
|
-
isComprehensiveReviewTool,
|
|
16
|
-
isReviewCommand,
|
|
17
|
-
mergeSignals,
|
|
18
|
-
summarizeSignals,
|
|
19
|
-
} from "../src/signals.js";
|
|
20
|
-
import type { ReviewSignal } from "../src/types.js";
|
|
21
|
-
|
|
22
|
-
describe("isComprehensiveReviewTool", () => {
|
|
23
|
-
it("knows the review tools", () => {
|
|
24
|
-
expect(isComprehensiveReviewTool("code-reviewer")).toBe(true);
|
|
25
|
-
expect(isComprehensiveReviewTool("code_reviewer")).toBe(true);
|
|
26
|
-
expect(isComprehensiveReviewTool("differential-review")).toBe(true);
|
|
27
|
-
expect(isComprehensiveReviewTool("semgrep")).toBe(true);
|
|
28
|
-
expect(isComprehensiveReviewTool("codeql")).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("matches a namespaced tool by its bare name", () => {
|
|
32
|
-
expect(isComprehensiveReviewTool("mcp__pi__code-review")).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("does not match a tool that only mentions a review", () => {
|
|
36
|
-
expect(isComprehensiveReviewTool("read_audit_log")).toBe(false);
|
|
37
|
-
expect(isComprehensiveReviewTool("review_status")).toBe(false);
|
|
38
|
-
expect(isComprehensiveReviewTool("semgrep_scan")).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("leaves the task-level checks alone", () => {
|
|
42
|
-
expect(isComprehensiveReviewTool("lens_diagnostics")).toBe(false);
|
|
43
|
-
expect(isComprehensiveReviewTool("bash")).toBe(false);
|
|
44
|
-
expect(isComprehensiveReviewTool("edit")).toBe(false);
|
|
45
|
-
expect(isComprehensiveReviewTool("todo")).toBe(false);
|
|
46
|
-
expect(isComprehensiveReviewTool("read")).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("accepts extra patterns from the deployment", () => {
|
|
50
|
-
expect(isComprehensiveReviewTool("house_review", ["house_review"])).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe("isReviewCommand", () => {
|
|
55
|
-
it("knows a review runner", () => {
|
|
56
|
-
expect(isReviewCommand("bun run code-review")).toBe(true);
|
|
57
|
-
expect(isReviewCommand("semgrep scan --config auto")).toBe(true);
|
|
58
|
-
expect(isReviewCommand("npx differential-review")).toBe(true);
|
|
59
|
-
expect(isReviewCommand("bun test && code-review")).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("ignores an ordinary command", () => {
|
|
63
|
-
expect(isReviewCommand("bun test")).toBe(false);
|
|
64
|
-
expect(isReviewCommand("npx biome check index.ts")).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("ignores a runner that is only mentioned", () => {
|
|
68
|
-
expect(isReviewCommand("echo code-review")).toBe(false);
|
|
69
|
-
expect(isReviewCommand("grep security-review README.md")).toBe(false);
|
|
70
|
-
expect(isReviewCommand("# semgrep scan")).toBe(false);
|
|
71
|
-
expect(isReviewCommand("cat notes.md | grep semgrep")).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe("dispatchAgentName", () => {
|
|
76
|
-
it("reads the agent of a dispatch tool", () => {
|
|
77
|
-
expect(dispatchAgentName("subagent", { agent: "code-reviewer" })).toBe("code-reviewer");
|
|
78
|
-
expect(dispatchAgentName("subagent", { agent: "researcher" })).toBe("researcher");
|
|
79
|
-
expect(dispatchAgentName("bash", { command: "ls" })).toBe("");
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe("detectReviewInvocation", () => {
|
|
84
|
-
it("recognizes a review tool", () => {
|
|
85
|
-
const invocation = detectReviewInvocation("code-reviewer", { files: ["index.ts"] });
|
|
86
|
-
expect(invocation?.source).toBe("code-reviewer");
|
|
87
|
-
expect(invocation?.files).toEqual(["index.ts"]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("recognizes a reviewer that was dispatched as an agent", () => {
|
|
91
|
-
const invocation = detectReviewInvocation("subagent", { agent: "code-reviewer" });
|
|
92
|
-
expect(invocation?.source).toBe("code-reviewer");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("recognizes a review runner in the shell", () => {
|
|
96
|
-
const invocation = detectReviewInvocation("bash", {
|
|
97
|
-
command: "bun run code-review --scope packages/notification",
|
|
98
|
-
});
|
|
99
|
-
expect(invocation?.source).toBe("bash");
|
|
100
|
-
expect(invocation?.reason).toContain("code-review");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("does not raise a signal for a task-level check", () => {
|
|
104
|
-
expect(detectReviewInvocation("lens_diagnostics", { paths: ["index.ts"] })).toBeNull();
|
|
105
|
-
expect(detectReviewInvocation("bash", { command: "npx tsc --noEmit" })).toBeNull();
|
|
106
|
-
expect(detectReviewInvocation("edit", { path: "index.ts" })).toBeNull();
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe("extractFiles", () => {
|
|
111
|
-
it("reads the files of a tool input", () => {
|
|
112
|
-
expect(extractFiles("code-reviewer", { files: ["a.ts", "b.ts"] })).toEqual(["a.ts", "b.ts"]);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("reads the files of a shell command", () => {
|
|
116
|
-
const files = extractFiles("bash", { command: "eslint packages/notification/index.ts" });
|
|
117
|
-
expect(files).toContain("packages/notification/index.ts");
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("explicitReviewRequest", () => {
|
|
122
|
-
it("sees an explicit request of the user", () => {
|
|
123
|
-
expect(explicitReviewRequest("please review the whole diff now")).toBe(true);
|
|
124
|
-
expect(explicitReviewRequest("run an audit of the repository")).toBe(true);
|
|
125
|
-
expect(explicitReviewRequest("review the task diff")).toBe(true);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("sees no request in an ordinary message", () => {
|
|
129
|
-
expect(explicitReviewRequest("fix the failing test")).toBe(false);
|
|
130
|
-
expect(explicitReviewRequest("")).toBe(false);
|
|
131
|
-
expect(explicitReviewRequest("the review notes mention a stale cache")).toBe(false);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("respects a negation", () => {
|
|
135
|
-
expect(explicitReviewRequest("do not review yet, continue the implementation")).toBe(false);
|
|
136
|
-
expect(explicitReviewRequest("no review for now")).toBe(false);
|
|
137
|
-
expect(explicitReviewRequest("skip the audit")).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("reads every sentence on its own", () => {
|
|
141
|
-
expect(explicitReviewRequest("Do not review yet. Please review the diff afterwards.")).toBe(
|
|
142
|
-
true,
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe("mergeSignals", () => {
|
|
148
|
-
const signal: ReviewSignal = {
|
|
149
|
-
source: "code-reviewer",
|
|
150
|
-
files: ["index.ts"],
|
|
151
|
-
reason: "a comprehensive review was requested",
|
|
152
|
-
at: "2026-10-25T00:00:00.000Z",
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
it("records a signal once", () => {
|
|
156
|
-
const once = mergeSignals([], signal);
|
|
157
|
-
const twice = mergeSignals(once, signal);
|
|
158
|
-
expect(twice).toHaveLength(1);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("keeps a different signal", () => {
|
|
162
|
-
const merged = mergeSignals(mergeSignals([], signal), {
|
|
163
|
-
...signal,
|
|
164
|
-
source: "semgrep",
|
|
165
|
-
});
|
|
166
|
-
expect(merged).toHaveLength(2);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("summarizes the signals into one scope", () => {
|
|
170
|
-
const merged = mergeSignals(mergeSignals([], signal), {
|
|
171
|
-
source: "semgrep",
|
|
172
|
-
files: ["other.ts"],
|
|
173
|
-
reason: "a second source",
|
|
174
|
-
at: "2026-10-25T00:01:00.000Z",
|
|
175
|
-
});
|
|
176
|
-
const summary = summarizeSignals(merged);
|
|
177
|
-
expect(summary.sources).toEqual(["code-reviewer", "semgrep"]);
|
|
178
|
-
expect(summary.files).toEqual(["index.ts", "other.ts"]);
|
|
179
|
-
expect(summary.reason).toContain("a second source");
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// touched
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The persisted state.
|
|
3
|
-
*
|
|
4
|
-
* The phase, the start revision, the scope, the finished chunks and the open
|
|
5
|
-
* findings survive a restart, so the workflow can be resumed after a
|
|
6
|
-
* compaction or a reset.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from "bun:test";
|
|
10
|
-
import { readdirSync, writeFileSync } from "node:fs";
|
|
11
|
-
import {
|
|
12
|
-
TwoPhaseStateStore,
|
|
13
|
-
allChunksDone,
|
|
14
|
-
createState,
|
|
15
|
-
hasQueuedReview,
|
|
16
|
-
markChunkDone,
|
|
17
|
-
markComplete,
|
|
18
|
-
moveToPhase,
|
|
19
|
-
normalizeState,
|
|
20
|
-
recordFinding,
|
|
21
|
-
recordSignal,
|
|
22
|
-
safeFileName,
|
|
23
|
-
startReview,
|
|
24
|
-
transitionToReviewFixes,
|
|
25
|
-
transitionToReviewPending,
|
|
26
|
-
} from "../src/state.js";
|
|
27
|
-
import type { ReviewChunk, ReviewSignal } from "../src/types.js";
|
|
28
|
-
import { withTempDir } from "./helpers.js";
|
|
29
|
-
|
|
30
|
-
/** A review chunk of the given id. */
|
|
31
|
-
function chunk(id: string): ReviewChunk {
|
|
32
|
-
return { id, label: `${id} (1 file)`, files: [`${id}.ts`], status: "pending" };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const signal: ReviewSignal = {
|
|
36
|
-
source: "code-reviewer",
|
|
37
|
-
files: ["index.ts"],
|
|
38
|
-
reason: "a comprehensive review was requested with code-reviewer",
|
|
39
|
-
at: "2026-10-25T00:00:00.000Z",
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
describe("createState", () => {
|
|
43
|
-
it("starts in the implementation phase", () => {
|
|
44
|
-
const state = createState("task-1", "abc123");
|
|
45
|
-
expect(state.phase).toBe("implementation");
|
|
46
|
-
expect(state.startRevision).toBe("abc123");
|
|
47
|
-
expect(state.signals).toEqual([]);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe("signals", () => {
|
|
52
|
-
it("records a signal once", () => {
|
|
53
|
-
const once = recordSignal(createState("t"), signal);
|
|
54
|
-
const twice = recordSignal(once, signal);
|
|
55
|
-
expect(twice.signals).toHaveLength(1);
|
|
56
|
-
expect(hasQueuedReview(twice)).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("keeps the phase while a signal is recorded", () => {
|
|
60
|
-
const state = recordSignal(createState("t"), signal);
|
|
61
|
-
expect(state.phase).toBe("implementation");
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("phases", () => {
|
|
66
|
-
it("moves forward only", () => {
|
|
67
|
-
const state = createState("t");
|
|
68
|
-
expect(moveToPhase(state, "review_pending").phase).toBe("review_pending");
|
|
69
|
-
expect(moveToPhase(state, "implementation").phase).toBe("implementation");
|
|
70
|
-
const review = moveToPhase(state, "review");
|
|
71
|
-
expect(moveToPhase(review, "implementation").phase).toBe("review");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("does not move back from the review into the implementation", () => {
|
|
75
|
-
const review = startReview(createState("t"), []);
|
|
76
|
-
expect(moveToPhase(review, "implementation").phase).toBe("review");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("enters the review with the scope and the chunks", () => {
|
|
80
|
-
const pending = transitionToReviewPending(createState("t"), ["a.ts", "b.ts"]);
|
|
81
|
-
expect(pending.phase).toBe("review_pending");
|
|
82
|
-
expect(pending.scopeFiles).toEqual(["a.ts", "b.ts"]);
|
|
83
|
-
|
|
84
|
-
const review = startReview(pending, [
|
|
85
|
-
{ id: "chunk-1", label: "packages/x (1 file)", files: ["a.ts"], status: "pending" },
|
|
86
|
-
]);
|
|
87
|
-
expect(review.phase).toBe("review");
|
|
88
|
-
expect(review.chunks).toHaveLength(1);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("marks the chunks as done", () => {
|
|
92
|
-
const review = startReview(createState("t"), [
|
|
93
|
-
{ id: "chunk-1", label: "a", files: ["a.ts"], status: "pending" },
|
|
94
|
-
{ id: "chunk-2", label: "b", files: ["b.ts"], status: "pending" },
|
|
95
|
-
]);
|
|
96
|
-
const one = markChunkDone(review, "chunk-1");
|
|
97
|
-
expect(allChunksDone(one)).toBe(false);
|
|
98
|
-
const both = markChunkDone(one, "chunk-2");
|
|
99
|
-
expect(allChunksDone(both)).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("enters the fixes only when the review is finished", () => {
|
|
103
|
-
const review = startReview(createState("t"), [chunk("chunk-1")]);
|
|
104
|
-
expect(transitionToReviewFixes(review).phase).toBe("review");
|
|
105
|
-
|
|
106
|
-
const withFinding = recordFinding(review, {
|
|
107
|
-
id: "finding-1",
|
|
108
|
-
severity: "medium",
|
|
109
|
-
summary: "the callback is not idempotent",
|
|
110
|
-
at: "2026-10-25T00:00:00.000Z",
|
|
111
|
-
});
|
|
112
|
-
// The review has not run over every chunk yet.
|
|
113
|
-
expect(transitionToReviewFixes(withFinding).phase).toBe("review");
|
|
114
|
-
|
|
115
|
-
const reviewed = markChunkDone(withFinding, "chunk-1");
|
|
116
|
-
expect(transitionToReviewFixes(reviewed).phase).toBe("review_fixes");
|
|
117
|
-
expect(withFinding.findings).toHaveLength(1);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("marks the task complete only when the review ran", () => {
|
|
121
|
-
// The implementation alone never completes the task.
|
|
122
|
-
expect(markComplete(createState("t")).phase).toBe("implementation");
|
|
123
|
-
|
|
124
|
-
const review = startReview(createState("t"), [chunk("chunk-1")]);
|
|
125
|
-
expect(markComplete(review).phase).toBe("review");
|
|
126
|
-
|
|
127
|
-
const reviewed = markChunkDone(review, "chunk-1");
|
|
128
|
-
expect(markComplete(reviewed).phase).toBe("complete");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("never moves the phase backwards", () => {
|
|
132
|
-
const review = startReview(createState("t"), [chunk("chunk-1")]);
|
|
133
|
-
const done = markComplete(markChunkDone(review, "chunk-1"));
|
|
134
|
-
expect(done.phase).toBe("complete");
|
|
135
|
-
// A late transition cannot undo the completion.
|
|
136
|
-
expect(transitionToReviewFixes(done).phase).toBe("complete");
|
|
137
|
-
expect(moveToPhase(done, "review").phase).toBe("complete");
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe("TwoPhaseStateStore", () => {
|
|
142
|
-
it("writes and reads the state of a task", () => {
|
|
143
|
-
withTempDir("state", (dir) => {
|
|
144
|
-
const store = new TwoPhaseStateStore(dir);
|
|
145
|
-
const state = recordSignal(createState("task-1", "rev1"), signal);
|
|
146
|
-
store.write(state);
|
|
147
|
-
|
|
148
|
-
const read = store.read("task-1");
|
|
149
|
-
expect(read?.phase).toBe("implementation");
|
|
150
|
-
expect(read?.signals).toHaveLength(1);
|
|
151
|
-
expect(read?.startRevision).toBe("rev1");
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("returns nothing for an unknown task", () => {
|
|
156
|
-
withTempDir("state", (dir) => {
|
|
157
|
-
const store = new TwoPhaseStateStore(dir);
|
|
158
|
-
expect(store.read("unknown")).toBeNull();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("reports a corrupt state instead of losing it", () => {
|
|
163
|
-
withTempDir("state", (dir) => {
|
|
164
|
-
const store = new TwoPhaseStateStore(dir);
|
|
165
|
-
store.write(createState("task-1", "rev1"));
|
|
166
|
-
writeFileSync(store.path("task-1"), "{not json", "utf8");
|
|
167
|
-
|
|
168
|
-
// A corrupt state must not look like a fresh task: that would lose
|
|
169
|
-
// the revision the task started at and the progress of the review.
|
|
170
|
-
expect(() => store.read("task-1")).toThrow(/not valid JSON/);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("leaves no temporary file behind", () => {
|
|
175
|
-
withTempDir("state", (dir) => {
|
|
176
|
-
const store = new TwoPhaseStateStore(dir);
|
|
177
|
-
store.write(createState("task-1"));
|
|
178
|
-
store.write(createState("task-1"));
|
|
179
|
-
expect(readdirSync(dir).filter((entry) => entry.endsWith(".tmp"))).toEqual([]);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("keeps the tasks apart", () => {
|
|
184
|
-
withTempDir("state", (dir) => {
|
|
185
|
-
const store = new TwoPhaseStateStore(dir);
|
|
186
|
-
store.write(recordSignal(createState("task-a"), signal));
|
|
187
|
-
store.write(createState("task-b"));
|
|
188
|
-
expect(store.read("task-a")?.signals).toHaveLength(1);
|
|
189
|
-
expect(store.read("task-b")?.signals).toHaveLength(0);
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("repairs a broken state", () => {
|
|
194
|
-
expect(normalizeState({ phase: "nonsense" }, "task-1").phase).toBe("implementation");
|
|
195
|
-
expect(normalizeState(null, "task-1").taskId).toBe("task-1");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("uses a safe file name", () => {
|
|
199
|
-
expect(safeFileName("session/with spaces")).toBe("session_with_spaces");
|
|
200
|
-
});
|
|
201
|
-
});
|