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,813 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt Compiler - Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests cover all acceptance criteria from RFC-0041:
|
|
5
|
-
* 1. Identical inputs produce identical prompt content and hash.
|
|
6
|
-
* 2. Required sections are never removed by compaction.
|
|
7
|
-
* 3. Provider formatting does not alter task semantics.
|
|
8
|
-
* 4. Secrets are redacted before persistence.
|
|
9
|
-
* 5. Invalid output contracts fail compilation.
|
|
10
|
-
* 6. Continuation prompts do not repeat completed work.
|
|
11
|
-
* 7. Unit tests cover all error codes.
|
|
12
|
-
* 8. Tests pass without network access.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import assert from "node:assert";
|
|
16
|
-
import { describe, it } from "node:test";
|
|
17
|
-
|
|
18
|
-
import type { CompiledRequirement } from "@pi-harness/requirement-compiler";
|
|
19
|
-
import type { CompiledTask } from "@pi-harness/task-compiler";
|
|
20
|
-
import { compactToBudget, estimateTokens } from "../src/budget.js";
|
|
21
|
-
// Import directly from source (TypeScript modules)
|
|
22
|
-
import { compilePrompt, createSyncHasher } from "../src/compiler.js";
|
|
23
|
-
import {
|
|
24
|
-
deduplicateSections,
|
|
25
|
-
normalizeForDeduplication,
|
|
26
|
-
} from "../src/deduplicate.js";
|
|
27
|
-
import { normalizeRequest } from "../src/normalize.js";
|
|
28
|
-
import { renderForProvider } from "../src/render.js";
|
|
29
|
-
import { buildSections } from "../src/section-builder.js";
|
|
30
|
-
import { PromptCompileError } from "../src/types.js";
|
|
31
|
-
import { PROVIDER_PROFILES } from "../src/types.js";
|
|
32
|
-
import type { PromptCompileRequest } from "../src/types.js";
|
|
33
|
-
import { validateSections, validateTokenBudget } from "../src/validate.js";
|
|
34
|
-
|
|
35
|
-
// --- Test fixtures -------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
function makeRequest(
|
|
38
|
-
overrides?: Partial<PromptCompileRequest>,
|
|
39
|
-
): PromptCompileRequest {
|
|
40
|
-
const requirement: CompiledRequirement = {
|
|
41
|
-
id: "REQ-001",
|
|
42
|
-
title: "Build login feature",
|
|
43
|
-
problemStatement: "Implement user login with JWT",
|
|
44
|
-
goals: [{ id: "g1", description: "Build login" }],
|
|
45
|
-
constraints: [
|
|
46
|
-
{ id: "c1", description: "Use HTTPS only", blocking: true },
|
|
47
|
-
{ id: "c2", description: "Passwords hashed with bcrypt", blocking: true },
|
|
48
|
-
],
|
|
49
|
-
acceptanceCriteria: [
|
|
50
|
-
{ id: "ac1", outcome: ["User can log in with valid credentials"] },
|
|
51
|
-
{ id: "ac2", outcome: ["Invalid credentials show error"] },
|
|
52
|
-
],
|
|
53
|
-
riskTags: [],
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const task: CompiledTask = {
|
|
57
|
-
id: "task-001",
|
|
58
|
-
type: "implementation",
|
|
59
|
-
title: "Implement login",
|
|
60
|
-
objective: "Implement user login feature with JWT authentication",
|
|
61
|
-
dependencies: [],
|
|
62
|
-
priority: 1,
|
|
63
|
-
fileOwnership: {
|
|
64
|
-
taskId: "task-001",
|
|
65
|
-
mode: "exclusive",
|
|
66
|
-
include: ["/project/src/auth/**"],
|
|
67
|
-
exclude: [],
|
|
68
|
-
},
|
|
69
|
-
filesInScope: [
|
|
70
|
-
{ path: "/project/src/auth/login.ts", description: "Login handler" },
|
|
71
|
-
{ path: "/project/src/auth/jwt.ts", description: "JWT utilities" },
|
|
72
|
-
],
|
|
73
|
-
estimatedComplexity: 3 as const,
|
|
74
|
-
expectedOutputs: [
|
|
75
|
-
{
|
|
76
|
-
kind: "file",
|
|
77
|
-
description: "login.ts implementation",
|
|
78
|
-
path: "/project/src/auth/login.ts",
|
|
79
|
-
required: true,
|
|
80
|
-
},
|
|
81
|
-
{ kind: "test_result", description: "login tests", required: true },
|
|
82
|
-
],
|
|
83
|
-
prohibitedCommands: ["rm -rf /", "yarn build"],
|
|
84
|
-
permittedCommands: ["tsc", "jest", "node"],
|
|
85
|
-
requiredCapabilities: ["typescript", "node"],
|
|
86
|
-
acceptanceCriteria: ["ac1", "ac2"],
|
|
87
|
-
jobId: "JOB-001",
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
task,
|
|
92
|
-
requirement,
|
|
93
|
-
context: { entries: [] },
|
|
94
|
-
provider: "codex",
|
|
95
|
-
attempt: 1,
|
|
96
|
-
...overrides,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function makeDeps(overrides?: {
|
|
101
|
-
projectRules?: string[];
|
|
102
|
-
redactor?: (content: string) => string;
|
|
103
|
-
}) {
|
|
104
|
-
const hasher = createSyncHasher();
|
|
105
|
-
return {
|
|
106
|
-
hasher,
|
|
107
|
-
clock: { now: () => new Date("2026-01-01T00:00:00.000Z") },
|
|
108
|
-
redactor: {
|
|
109
|
-
redact: overrides?.redactor ?? ((s: string) => s),
|
|
110
|
-
},
|
|
111
|
-
projectRules: overrides?.projectRules ?? [
|
|
112
|
-
"Use TypeScript strict mode",
|
|
113
|
-
"Write unit tests for all public functions",
|
|
114
|
-
],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// --- Test 1: Deterministic hash --------------------------------------
|
|
119
|
-
|
|
120
|
-
describe("Deterministic hash", () => {
|
|
121
|
-
it("identical inputs produce identical prompt content and hash", async () => {
|
|
122
|
-
const request = makeRequest();
|
|
123
|
-
const deps = makeDeps();
|
|
124
|
-
|
|
125
|
-
const pkg1 = await compilePrompt(request, deps);
|
|
126
|
-
const pkg2 = await compilePrompt(request, deps);
|
|
127
|
-
|
|
128
|
-
assert.strictEqual(pkg1.hash, pkg2.hash, "Hash should be deterministic");
|
|
129
|
-
assert.strictEqual(pkg1.system, pkg2.system, "System prompt should match");
|
|
130
|
-
assert.strictEqual(pkg1.user, pkg2.user, "User prompt should match");
|
|
131
|
-
assert.strictEqual(pkg1.taskId, pkg2.taskId);
|
|
132
|
-
assert.strictEqual(pkg1.provider, pkg2.provider);
|
|
133
|
-
assert.strictEqual(pkg1.version, "1");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("different content produces different hash", async () => {
|
|
137
|
-
const request1 = makeRequest();
|
|
138
|
-
const request2 = makeRequest({
|
|
139
|
-
task: {
|
|
140
|
-
...request1.task,
|
|
141
|
-
objective: "Different objective",
|
|
142
|
-
} as CompiledTask,
|
|
143
|
-
});
|
|
144
|
-
const deps = makeDeps();
|
|
145
|
-
|
|
146
|
-
const pkg1 = await compilePrompt(request1, deps);
|
|
147
|
-
const pkg2 = await compilePrompt(request2, deps);
|
|
148
|
-
|
|
149
|
-
assert.notStrictEqual(
|
|
150
|
-
pkg1.hash,
|
|
151
|
-
pkg2.hash,
|
|
152
|
-
"Different content should produce different hash",
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("hash includes both system and user content", async () => {
|
|
157
|
-
const request = makeRequest();
|
|
158
|
-
const deps = makeDeps();
|
|
159
|
-
|
|
160
|
-
const pkg = await compilePrompt(request, deps);
|
|
161
|
-
|
|
162
|
-
assert.ok(pkg.hash.length > 0, "Hash should not be empty");
|
|
163
|
-
assert.ok(
|
|
164
|
-
pkg.hash === deps.hasher.sha256(`${pkg.system}\n${pkg.user}`),
|
|
165
|
-
"Hash should equal SHA-256 of system+user",
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// --- Test 2: Required sections never removed -----------------------------
|
|
171
|
-
|
|
172
|
-
describe("Required sections survive compaction", () => {
|
|
173
|
-
it("objective section is required and never compacted", async () => {
|
|
174
|
-
const request = makeRequest({
|
|
175
|
-
context: {
|
|
176
|
-
entries: Array.from({ length: 50 }, (_, i) => ({
|
|
177
|
-
id: `ctx-${i}`,
|
|
178
|
-
content: `This is supplemental context entry number ${i}. It provides background information that could be safely removed if needed.`,
|
|
179
|
-
priority: 7, // supplemental priority
|
|
180
|
-
source: `file-${i}.md`,
|
|
181
|
-
})),
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
const deps = makeDeps();
|
|
185
|
-
|
|
186
|
-
const pkg = await compilePrompt(request, deps);
|
|
187
|
-
|
|
188
|
-
assert.ok(
|
|
189
|
-
pkg.user.includes("Implement user login feature"),
|
|
190
|
-
"Objective should be in user prompt",
|
|
191
|
-
);
|
|
192
|
-
assert.ok(
|
|
193
|
-
pkg.user.includes("User can log in with valid credentials"),
|
|
194
|
-
"Acceptance criteria should be preserved",
|
|
195
|
-
);
|
|
196
|
-
assert.ok(
|
|
197
|
-
pkg.user.includes("/project/src/auth/login.ts"),
|
|
198
|
-
"Files in scope should be preserved",
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("project rules section is required", async () => {
|
|
203
|
-
const request = makeRequest();
|
|
204
|
-
const deps = makeDeps({
|
|
205
|
-
projectRules: ["Use TypeScript", "No console.log"],
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const pkg = await compilePrompt(request, deps);
|
|
209
|
-
|
|
210
|
-
assert.ok(
|
|
211
|
-
pkg.system.includes("TypeScript") || pkg.user.includes("TypeScript"),
|
|
212
|
-
"Project rules should be in prompt",
|
|
213
|
-
);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("acceptance criteria are never removed", async () => {
|
|
217
|
-
const request = makeRequest();
|
|
218
|
-
const deps = makeDeps();
|
|
219
|
-
|
|
220
|
-
const pkg = await compilePrompt(request, deps);
|
|
221
|
-
|
|
222
|
-
const acSection = pkg.sections.find(
|
|
223
|
-
(s) => s.kind === "acceptance_criteria",
|
|
224
|
-
);
|
|
225
|
-
assert.ok(acSection, "AC section should exist");
|
|
226
|
-
assert.strictEqual(
|
|
227
|
-
acSection?.required,
|
|
228
|
-
true,
|
|
229
|
-
"AC section should be required",
|
|
230
|
-
);
|
|
231
|
-
assert.ok(
|
|
232
|
-
pkg.user.includes("User can log in with valid credentials"),
|
|
233
|
-
"AC text should be in prompt",
|
|
234
|
-
);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// --- Test 3: Provider formatting ---------------------------------------
|
|
239
|
-
|
|
240
|
-
describe("Provider formatting", () => {
|
|
241
|
-
it("anthropic uses XML formatting", async () => {
|
|
242
|
-
const request = makeRequest({ provider: "anthropic" });
|
|
243
|
-
const deps = makeDeps();
|
|
244
|
-
|
|
245
|
-
const pkg = await compilePrompt(request, deps);
|
|
246
|
-
const profile = PROVIDER_PROFILES.anthropic;
|
|
247
|
-
|
|
248
|
-
const sections = buildSections(
|
|
249
|
-
normalizeRequest(request),
|
|
250
|
-
deps.projectRules,
|
|
251
|
-
);
|
|
252
|
-
const rendered = renderForProvider(sections, profile);
|
|
253
|
-
|
|
254
|
-
// Anthropic's rules go in system
|
|
255
|
-
if (profile.supportsSystemPrompt) {
|
|
256
|
-
assert.ok(
|
|
257
|
-
rendered.system.length > 0 || rendered.user.length > 0,
|
|
258
|
-
"Should have some output",
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("all supported providers produce valid output", async () => {
|
|
264
|
-
const request = makeRequest();
|
|
265
|
-
const providers: Array<
|
|
266
|
-
"codex" | "minimax" | "glm" | "openai" | "anthropic" | "gemini"
|
|
267
|
-
> = ["codex", "minimax", "glm", "openai", "anthropic", "gemini"];
|
|
268
|
-
|
|
269
|
-
for (const provider of providers) {
|
|
270
|
-
const req = makeRequest({ provider });
|
|
271
|
-
const deps = makeDeps();
|
|
272
|
-
const pkg = await compilePrompt(req, deps);
|
|
273
|
-
|
|
274
|
-
assert.ok(
|
|
275
|
-
pkg.system.length > 0 || pkg.user.length > 0,
|
|
276
|
-
`Provider ${provider} should produce output`,
|
|
277
|
-
);
|
|
278
|
-
assert.strictEqual(pkg.provider, provider);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("different styles produce different formatting but same content", async () => {
|
|
283
|
-
const request = makeRequest({ provider: "openai" });
|
|
284
|
-
const deps = makeDeps();
|
|
285
|
-
|
|
286
|
-
const pkg1 = await compilePrompt(request, deps);
|
|
287
|
-
const req2 = makeRequest({ provider: "anthropic" });
|
|
288
|
-
const pkg2 = await compilePrompt(req2, deps);
|
|
289
|
-
|
|
290
|
-
// Both should contain the same key content
|
|
291
|
-
assert.ok(
|
|
292
|
-
pkg1.user.includes("Implement user login"),
|
|
293
|
-
"OpenAI should have objective",
|
|
294
|
-
);
|
|
295
|
-
assert.ok(
|
|
296
|
-
pkg2.user.includes("Implement user login"),
|
|
297
|
-
"Anthropic should have objective",
|
|
298
|
-
);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// --- Test 4: Secret redaction -----------------------------------------
|
|
303
|
-
|
|
304
|
-
describe("Secret redaction", () => {
|
|
305
|
-
it("API keys are redacted before persistence", async () => {
|
|
306
|
-
const request = makeRequest();
|
|
307
|
-
const deps = makeDeps({
|
|
308
|
-
redactor: (content: string) =>
|
|
309
|
-
content
|
|
310
|
-
.replace(/sk-[a-zA-Z0-9]{20,}/g, "[API_KEY_REDACTED]")
|
|
311
|
-
.replace(/password\s*=\s*["'][^"']+["']/gi, 'password="[REDACTED]"'),
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
const secretRequest = makeRequest({
|
|
315
|
-
task: {
|
|
316
|
-
...request.task,
|
|
317
|
-
objective: "Use API key sk-1234567890abcdefghij for authentication",
|
|
318
|
-
permittedCommands: [
|
|
319
|
-
"curl -H 'Authorization: Bearer sk-abcdefghijklmnop12345' https://api.example.com",
|
|
320
|
-
],
|
|
321
|
-
} as CompiledTask,
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const pkg = await compilePrompt(secretRequest, deps);
|
|
325
|
-
|
|
326
|
-
assert.ok(
|
|
327
|
-
!pkg.user.includes("sk-1234567890abcdefghij"),
|
|
328
|
-
"API key should be redacted",
|
|
329
|
-
);
|
|
330
|
-
assert.ok(
|
|
331
|
-
!pkg.user.includes("sk-abcdefghijklmnop12345"),
|
|
332
|
-
"Bearer token should be redacted",
|
|
333
|
-
);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("identity section preserves task ID", async () => {
|
|
337
|
-
const request = makeRequest();
|
|
338
|
-
const deps = makeDeps();
|
|
339
|
-
|
|
340
|
-
const pkg = await compilePrompt(request, deps);
|
|
341
|
-
|
|
342
|
-
assert.ok(pkg.user.includes("task-001"), "Task ID should be preserved");
|
|
343
|
-
assert.ok(
|
|
344
|
-
pkg.user.includes("REQ-001"),
|
|
345
|
-
"Requirement ID should be preserved",
|
|
346
|
-
);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it("hash is computed over redacted content", async () => {
|
|
350
|
-
const request = makeRequest();
|
|
351
|
-
const deps = makeDeps({
|
|
352
|
-
redactor: (content: string) =>
|
|
353
|
-
content.replace(/sk-[a-zA-Z0-9]+/g, "[REDACTED]"),
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const pkg = await compilePrompt(request, deps);
|
|
357
|
-
|
|
358
|
-
assert.ok(pkg.hash.length > 0, "Hash should be computed");
|
|
359
|
-
assert.ok(!pkg.user.includes("sk-"), "No API keys in prompt");
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// --- Test 5: Error codes -----------------------------------------------
|
|
364
|
-
|
|
365
|
-
describe("Error codes", () => {
|
|
366
|
-
it("INVALID_TASK when task ID is empty", async () => {
|
|
367
|
-
const base = makeRequest();
|
|
368
|
-
const request = makeRequest({
|
|
369
|
-
task: { ...base.task, id: "" },
|
|
370
|
-
});
|
|
371
|
-
const deps = makeDeps();
|
|
372
|
-
|
|
373
|
-
await assert.rejects(
|
|
374
|
-
async () => compilePrompt(request, deps),
|
|
375
|
-
(err: unknown) => {
|
|
376
|
-
if (err instanceof PromptCompileError) {
|
|
377
|
-
assert.strictEqual(err.code, "INVALID_TASK");
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
return false;
|
|
381
|
-
},
|
|
382
|
-
);
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it("MISSING_OBJECTIVE when objective is empty", async () => {
|
|
386
|
-
const base = makeRequest();
|
|
387
|
-
const request = makeRequest({
|
|
388
|
-
task: { ...base.task, objective: "" },
|
|
389
|
-
});
|
|
390
|
-
const deps = makeDeps();
|
|
391
|
-
|
|
392
|
-
await assert.rejects(
|
|
393
|
-
async () => compilePrompt(request, deps),
|
|
394
|
-
(err: unknown) => {
|
|
395
|
-
if (err instanceof PromptCompileError) {
|
|
396
|
-
assert.strictEqual(err.code, "MISSING_OBJECTIVE");
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
return false;
|
|
400
|
-
},
|
|
401
|
-
);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it("MISSING_OUTPUT_CONTRACT when no outputs defined", async () => {
|
|
405
|
-
const base = makeRequest();
|
|
406
|
-
const request = makeRequest({
|
|
407
|
-
task: { ...base.task, expectedOutputs: [] },
|
|
408
|
-
});
|
|
409
|
-
const deps = makeDeps();
|
|
410
|
-
|
|
411
|
-
await assert.rejects(
|
|
412
|
-
async () => compilePrompt(request, deps),
|
|
413
|
-
(err: unknown) => {
|
|
414
|
-
if (err instanceof PromptCompileError) {
|
|
415
|
-
assert.strictEqual(err.code, "MISSING_OUTPUT_CONTRACT");
|
|
416
|
-
return true;
|
|
417
|
-
}
|
|
418
|
-
return false;
|
|
419
|
-
},
|
|
420
|
-
);
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
it("TOKEN_BUDGET_EXCEEDED when estimated tokens exceed budget", async () => {
|
|
424
|
-
// Test validation directly: compactToBudget removes all compactable content,
|
|
425
|
-
// so the full compile cannot produce TOKEN_BUDGET_EXCEEDED.
|
|
426
|
-
// We test validateTokenBudget directly.
|
|
427
|
-
const profile = PROVIDER_PROFILES.codex;
|
|
428
|
-
const available = profile.maximumInputTokens - profile.reservedOutputTokens;
|
|
429
|
-
|
|
430
|
-
assert.throws(
|
|
431
|
-
() => validateTokenBudget(available + 1, profile),
|
|
432
|
-
(err: unknown) => {
|
|
433
|
-
if (err instanceof PromptCompileError) {
|
|
434
|
-
assert.strictEqual(err.code, "TOKEN_BUDGET_EXCEEDED");
|
|
435
|
-
return true;
|
|
436
|
-
}
|
|
437
|
-
return false;
|
|
438
|
-
},
|
|
439
|
-
);
|
|
440
|
-
});
|
|
441
|
-
it("POLICY_CONFLICT for prohibited tools", async () => {
|
|
442
|
-
const base = makeRequest();
|
|
443
|
-
const request = makeRequest({
|
|
444
|
-
task: {
|
|
445
|
-
...base.task,
|
|
446
|
-
permittedCommands: ["rm -rf /", "tsc", "jest"],
|
|
447
|
-
},
|
|
448
|
-
});
|
|
449
|
-
const deps = makeDeps();
|
|
450
|
-
|
|
451
|
-
await assert.rejects(
|
|
452
|
-
async () => compilePrompt(request, deps),
|
|
453
|
-
(err: unknown) => {
|
|
454
|
-
if (err instanceof PromptCompileError) {
|
|
455
|
-
assert.strictEqual(err.code, "POLICY_CONFLICT");
|
|
456
|
-
return true;
|
|
457
|
-
}
|
|
458
|
-
return false;
|
|
459
|
-
},
|
|
460
|
-
);
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("INVALID_TASK for unknown provider", async () => {
|
|
464
|
-
const request = makeRequest({ provider: "unknown-provider" as "codex" });
|
|
465
|
-
const deps = makeDeps();
|
|
466
|
-
|
|
467
|
-
await assert.rejects(
|
|
468
|
-
async () => compilePrompt(request, deps),
|
|
469
|
-
(err: unknown) => {
|
|
470
|
-
if (err instanceof PromptCompileError) {
|
|
471
|
-
assert.strictEqual(err.code, "INVALID_TASK");
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
return false;
|
|
475
|
-
},
|
|
476
|
-
);
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// --- Test 6: Continuation without repetition ---------------------------
|
|
481
|
-
|
|
482
|
-
describe("Continuation without repetition", () => {
|
|
483
|
-
it("completed items are marked as done, not repeated", async () => {
|
|
484
|
-
const request = makeRequest({
|
|
485
|
-
continuation: {
|
|
486
|
-
previousResponsePath: "/path/to/previous/response.json",
|
|
487
|
-
completedItems: ["Step 1: Login form", "Step 2: JWT utility"],
|
|
488
|
-
incompleteItems: ["Step 3: Error handling"],
|
|
489
|
-
instruction: "continue_without_repeating",
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
const deps = makeDeps();
|
|
493
|
-
|
|
494
|
-
const pkg = await compilePrompt(request, deps);
|
|
495
|
-
|
|
496
|
-
assert.ok(
|
|
497
|
-
pkg.user.includes("Step 1: Login form") || pkg.user.includes("DONE"),
|
|
498
|
-
"Completed items should be marked",
|
|
499
|
-
);
|
|
500
|
-
assert.ok(
|
|
501
|
-
pkg.user.includes("Step 3: Error handling"),
|
|
502
|
-
"Incomplete items should be included",
|
|
503
|
-
);
|
|
504
|
-
assert.ok(
|
|
505
|
-
pkg.user.includes("Continue from where the previous attempt stopped"),
|
|
506
|
-
"Continuation instruction should be present",
|
|
507
|
-
);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it("continuation section has required=true so it survives compaction", async () => {
|
|
511
|
-
const request = makeRequest({
|
|
512
|
-
continuation: {
|
|
513
|
-
previousResponsePath: "/tmp/response.json",
|
|
514
|
-
completedItems: ["A", "B"],
|
|
515
|
-
incompleteItems: ["C"],
|
|
516
|
-
instruction: "continue_without_repeating",
|
|
517
|
-
},
|
|
518
|
-
});
|
|
519
|
-
const deps = makeDeps();
|
|
520
|
-
|
|
521
|
-
const pkg = await compilePrompt(request, deps);
|
|
522
|
-
|
|
523
|
-
const contSection = pkg.sections.find(
|
|
524
|
-
(s) => s.kind === "continuation_instructions",
|
|
525
|
-
);
|
|
526
|
-
assert.ok(contSection, "Continuation section should exist");
|
|
527
|
-
assert.strictEqual(
|
|
528
|
-
contSection?.required,
|
|
529
|
-
true,
|
|
530
|
-
"Continuation should be required",
|
|
531
|
-
);
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
it("previous response path is referenced", async () => {
|
|
535
|
-
const request = makeRequest({
|
|
536
|
-
continuation: {
|
|
537
|
-
previousResponsePath: "/tmp/attempt-1-response.json",
|
|
538
|
-
completedItems: [],
|
|
539
|
-
incompleteItems: ["Remaining work"],
|
|
540
|
-
instruction: "continue_without_repeating",
|
|
541
|
-
},
|
|
542
|
-
});
|
|
543
|
-
const deps = makeDeps();
|
|
544
|
-
|
|
545
|
-
const pkg = await compilePrompt(request, deps);
|
|
546
|
-
|
|
547
|
-
assert.ok(
|
|
548
|
-
pkg.user.includes("attempt-1-response.json") ||
|
|
549
|
-
pkg.sections.some((s) =>
|
|
550
|
-
s.sourceRefs.some((ref) => ref.source.includes("attempt-1")),
|
|
551
|
-
),
|
|
552
|
-
"Previous response path should be referenced",
|
|
553
|
-
);
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// --- Test 7: Token budget ----------------------------------------------
|
|
558
|
-
|
|
559
|
-
describe("Token budget", () => {
|
|
560
|
-
it("estimateTokens returns 0 for empty string", () => {
|
|
561
|
-
assert.strictEqual(estimateTokens(""), 0);
|
|
562
|
-
assert.strictEqual(estimateTokens(" "), 0);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
it("estimateTokens scales with content length", () => {
|
|
566
|
-
const short = "hello";
|
|
567
|
-
const long = "hello world this is a much longer string";
|
|
568
|
-
|
|
569
|
-
assert.ok(estimateTokens(long) > estimateTokens(short));
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
it("compactToBudget preserves required sections", () => {
|
|
573
|
-
const sections = [
|
|
574
|
-
{
|
|
575
|
-
id: "objective",
|
|
576
|
-
kind: "objective" as const,
|
|
577
|
-
content: "This is the critical objective",
|
|
578
|
-
required: true,
|
|
579
|
-
compactable: false,
|
|
580
|
-
sourceRefs: [],
|
|
581
|
-
},
|
|
582
|
-
{
|
|
583
|
-
id: "context-1",
|
|
584
|
-
kind: "supplemental" as const,
|
|
585
|
-
content: "This is supplemental content that can be removed",
|
|
586
|
-
required: false,
|
|
587
|
-
compactable: true,
|
|
588
|
-
sourceRefs: [],
|
|
589
|
-
},
|
|
590
|
-
];
|
|
591
|
-
|
|
592
|
-
const result = compactToBudget(sections, 100, estimateTokens);
|
|
593
|
-
|
|
594
|
-
const objectiveSection = result.sections.find((s) => s.id === "objective");
|
|
595
|
-
assert.ok(
|
|
596
|
-
objectiveSection?.content.includes("critical objective"),
|
|
597
|
-
"Required section should be preserved",
|
|
598
|
-
);
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
it("validateTokenBudget throws when exceeded", () => {
|
|
602
|
-
const profile = PROVIDER_PROFILES.codex;
|
|
603
|
-
const available = profile.maximumInputTokens - profile.reservedOutputTokens;
|
|
604
|
-
|
|
605
|
-
assert.throws(
|
|
606
|
-
() => validateTokenBudget(available + 1, profile),
|
|
607
|
-
(err: unknown) => {
|
|
608
|
-
if (err instanceof PromptCompileError) {
|
|
609
|
-
assert.strictEqual(err.code, "TOKEN_BUDGET_EXCEEDED");
|
|
610
|
-
return true;
|
|
611
|
-
}
|
|
612
|
-
return false;
|
|
613
|
-
},
|
|
614
|
-
);
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
it("validateTokenBudget passes when within budget", () => {
|
|
618
|
-
const profile = PROVIDER_PROFILES.codex;
|
|
619
|
-
const available = profile.maximumInputTokens - profile.reservedOutputTokens;
|
|
620
|
-
|
|
621
|
-
assert.doesNotThrow(() => validateTokenBudget(available, profile));
|
|
622
|
-
assert.doesNotThrow(() => validateTokenBudget(100, profile));
|
|
623
|
-
});
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
// --- Test 8: Deduplication ---------------------------------------------
|
|
627
|
-
|
|
628
|
-
describe("Deduplication", () => {
|
|
629
|
-
it("normalizeForDeduplication strips markdown and lowercases", () => {
|
|
630
|
-
assert.strictEqual(normalizeForDeduplication("# Header"), "header");
|
|
631
|
-
assert.strictEqual(normalizeForDeduplication("**bold** text"), "bold text");
|
|
632
|
-
assert.strictEqual(
|
|
633
|
-
normalizeForDeduplication("[link](http://example.com)"),
|
|
634
|
-
"link",
|
|
635
|
-
);
|
|
636
|
-
assert.strictEqual(
|
|
637
|
-
normalizeForDeduplication(" spaces collapse "),
|
|
638
|
-
"spaces collapse",
|
|
639
|
-
);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
it("duplicate lines are removed from compactable sections", () => {
|
|
643
|
-
const sections = [
|
|
644
|
-
{
|
|
645
|
-
id: "context-1",
|
|
646
|
-
kind: "relevant_context" as const,
|
|
647
|
-
content:
|
|
648
|
-
"Important note\nThis is repeated\nThis is repeated\nAnother line",
|
|
649
|
-
required: false,
|
|
650
|
-
compactable: true,
|
|
651
|
-
sourceRefs: [],
|
|
652
|
-
},
|
|
653
|
-
];
|
|
654
|
-
|
|
655
|
-
const result = deduplicateSections(sections);
|
|
656
|
-
|
|
657
|
-
assert.ok(
|
|
658
|
-
result[0]?.content.split("\n").filter(Boolean).length < 4,
|
|
659
|
-
"Duplicates should be removed",
|
|
660
|
-
);
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it("required sections are not deduplicated", () => {
|
|
664
|
-
const sections = [
|
|
665
|
-
{
|
|
666
|
-
id: "objective",
|
|
667
|
-
kind: "objective" as const,
|
|
668
|
-
content: "Same objective",
|
|
669
|
-
required: true,
|
|
670
|
-
compactable: false,
|
|
671
|
-
sourceRefs: [],
|
|
672
|
-
},
|
|
673
|
-
{
|
|
674
|
-
id: "objective-2",
|
|
675
|
-
kind: "objective" as const,
|
|
676
|
-
content: "Same objective",
|
|
677
|
-
required: true,
|
|
678
|
-
compactable: false,
|
|
679
|
-
sourceRefs: [],
|
|
680
|
-
},
|
|
681
|
-
];
|
|
682
|
-
|
|
683
|
-
const result = deduplicateSections(sections);
|
|
684
|
-
|
|
685
|
-
// Both required sections should be preserved
|
|
686
|
-
assert.strictEqual(
|
|
687
|
-
result.length,
|
|
688
|
-
2,
|
|
689
|
-
"Required sections should not be deduplicated",
|
|
690
|
-
);
|
|
691
|
-
});
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
// --- Test 9: Metadata completeness ------------------------------------
|
|
695
|
-
|
|
696
|
-
describe("Metadata completeness", () => {
|
|
697
|
-
it("PromptPackage includes token estimate and source refs", async () => {
|
|
698
|
-
const request = makeRequest({
|
|
699
|
-
context: {
|
|
700
|
-
entries: [
|
|
701
|
-
{
|
|
702
|
-
id: "ctx-1",
|
|
703
|
-
content: "Important context about the codebase",
|
|
704
|
-
priority: 1,
|
|
705
|
-
source: "repo/readme.md",
|
|
706
|
-
},
|
|
707
|
-
],
|
|
708
|
-
},
|
|
709
|
-
});
|
|
710
|
-
const deps = makeDeps();
|
|
711
|
-
|
|
712
|
-
const pkg = await compilePrompt(request, deps);
|
|
713
|
-
|
|
714
|
-
assert.ok(
|
|
715
|
-
pkg.estimatedInputTokens > 0,
|
|
716
|
-
"Token estimate should be positive",
|
|
717
|
-
);
|
|
718
|
-
assert.ok(pkg.sourceRefs.length > 0, "Should have source references");
|
|
719
|
-
assert.strictEqual(pkg.version, "1");
|
|
720
|
-
assert.strictEqual(pkg.createdAt, "2026-01-01T00:00:00.000Z");
|
|
721
|
-
assert.ok(pkg.hash.length > 0, "Hash should be present");
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
it("sections are ordered deterministically", async () => {
|
|
725
|
-
const request = makeRequest();
|
|
726
|
-
const deps = makeDeps();
|
|
727
|
-
|
|
728
|
-
const pkg = await compilePrompt(request, deps);
|
|
729
|
-
|
|
730
|
-
const kinds = pkg.sections.map((s) => s.kind);
|
|
731
|
-
// identity should come first
|
|
732
|
-
assert.strictEqual(kinds[0], "identity");
|
|
733
|
-
// objective should be early
|
|
734
|
-
assert.ok(
|
|
735
|
-
kinds.indexOf("objective") < kinds.indexOf("supplemental") ||
|
|
736
|
-
!kinds.includes("supplemental"),
|
|
737
|
-
);
|
|
738
|
-
});
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
// --- Test 10: PromptCompileError -------------------------------------
|
|
742
|
-
|
|
743
|
-
describe("PromptCompileError", () => {
|
|
744
|
-
it("has correct name, code, and message", () => {
|
|
745
|
-
const error = new PromptCompileError(
|
|
746
|
-
"TOKEN_BUDGET_EXCEEDED",
|
|
747
|
-
"Budget exceeded",
|
|
748
|
-
{ estimated: 1000, available: 500 },
|
|
749
|
-
);
|
|
750
|
-
|
|
751
|
-
assert.strictEqual(error.name, "PromptCompileError");
|
|
752
|
-
assert.strictEqual(error.code, "TOKEN_BUDGET_EXCEEDED");
|
|
753
|
-
assert.strictEqual(error.message, "Budget exceeded");
|
|
754
|
-
assert.deepStrictEqual(error.details, { estimated: 1000, available: 500 });
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
it("is instance of Error", () => {
|
|
758
|
-
const error = new PromptCompileError("INVALID_TASK", "Bad task");
|
|
759
|
-
assert.ok(error instanceof Error);
|
|
760
|
-
assert.ok(error instanceof PromptCompileError);
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
// --- Test 11: normalizeRequest ----------------------------------------
|
|
765
|
-
|
|
766
|
-
describe("normalizeRequest", () => {
|
|
767
|
-
it("trims strings", () => {
|
|
768
|
-
const req = makeRequest({
|
|
769
|
-
task: { ...makeRequest().task, id: " task-001 " } as CompiledTask,
|
|
770
|
-
});
|
|
771
|
-
const normalized = normalizeRequest(req);
|
|
772
|
-
assert.strictEqual(normalized.taskId, "task-001");
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
it("sorts context entries by priority", () => {
|
|
776
|
-
const req = makeRequest({
|
|
777
|
-
context: {
|
|
778
|
-
entries: [
|
|
779
|
-
{ id: "a", content: "Low priority", priority: 5, source: "x" },
|
|
780
|
-
{ id: "b", content: "High priority", priority: 1, source: "y" },
|
|
781
|
-
{ id: "c", content: "Medium priority", priority: 3, source: "z" },
|
|
782
|
-
],
|
|
783
|
-
},
|
|
784
|
-
});
|
|
785
|
-
const normalized = normalizeRequest(req);
|
|
786
|
-
|
|
787
|
-
assert.strictEqual(normalized.contextEntries[0]?.id, "b");
|
|
788
|
-
assert.strictEqual(normalized.contextEntries[1]?.id, "c");
|
|
789
|
-
assert.strictEqual(normalized.contextEntries[2]?.id, "a");
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
it("removes duplicates from arrays", () => {
|
|
793
|
-
const req = makeRequest();
|
|
794
|
-
const withDupes = normalizeRequest({
|
|
795
|
-
...req,
|
|
796
|
-
context: {
|
|
797
|
-
entries: [
|
|
798
|
-
{ id: "a", content: "Content A", priority: 1, source: "x" },
|
|
799
|
-
{ id: "b", content: "Content A", priority: 1, source: "y" }, // duplicate content
|
|
800
|
-
],
|
|
801
|
-
},
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
const uniqueContents = new Set(
|
|
805
|
-
withDupes.contextEntries.map((e) => e.content),
|
|
806
|
-
);
|
|
807
|
-
assert.strictEqual(
|
|
808
|
-
uniqueContents.size,
|
|
809
|
-
1,
|
|
810
|
-
"Duplicate content should be removed",
|
|
811
|
-
);
|
|
812
|
-
});
|
|
813
|
-
});
|