@1aboveio/skills 1.0.7 → 1.0.9
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/.codex-plugin/plugin.json +13 -0
- package/.codex-plugin/resources.json +1827 -0
- package/README.md +22 -0
- package/package.json +9 -2
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/airflow/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/SKILL.md +92 -34
- package/skills/app-dev/android-foundation/assets/android-ci-job.yml +13 -7
- package/skills/app-dev/android-foundation/references/ci.md +23 -13
- package/skills/app-dev/android-foundation/references/delivery-focus.md +29 -0
- package/skills/app-dev/android-foundation/references/diagnosis.md +136 -0
- package/skills/app-dev/android-foundation/references/gotchas.md +20 -11
- package/skills/app-dev/android-foundation/references/setup.md +15 -6
- package/skills/app-dev/android-foundation/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/scripts/device-run.sh +148 -42
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/app-debug/release-check.mjs +3 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/better-auth/release-check.mjs +3 -1
- package/skills/app-dev/data-catalog/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/release-check.mjs +3 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/data-quality/release-check.mjs +3 -1
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/monorepo/release-check.mjs +3 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/pyspark/release-check.mjs +3 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/python-backend/release-check.mjs +3 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/shadcn/release-check.mjs +3 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/release-check.mjs +3 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-build/release-check.mjs +3 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/release-check.mjs +3 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/docker/release-check.mjs +3 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-actions/release-check.mjs +3 -1
- package/skills/cicd/github-flow/SKILL.md +1 -1
- package/skills/cicd/github-flow/release-check.mjs +3 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/google-cloud/release-check.mjs +3 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/mergify/release-check.mjs +3 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/cicd/podman/release-check.mjs +3 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-review/release-check.mjs +3 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/article-to-image/release-check.mjs +3 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/flowchart-svg/release-check.mjs +3 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/image-gen/release-check.mjs +3 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/mermaid/release-check.mjs +3 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/release-check.mjs +3 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-publisher/release-check.mjs +3 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/wechat-search/release-check.mjs +3 -1
- package/skills/content/xai-search/SKILL.md +88 -13
- package/skills/content/xai-search/release-check.mjs +3 -1
- package/skills/content/xai-search/scripts/xai_search.sh +125 -149
- package/skills/core/distill-writing/SKILL.md +1 -1
- package/skills/core/distill-writing/release-check.mjs +3 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/doc-to-md/release-check.mjs +3 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/editorial-review/release-check.mjs +3 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/house-style/release-check.mjs +3 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/plain-language/release-check.mjs +3 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/polish-cn/release-check.mjs +3 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/reg-parser/release-check.mjs +3 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/terminology/release-check.mjs +3 -1
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-doc/release-check.mjs +3 -1
- package/skills/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/better-review/release-check.mjs +3 -1
- package/skills/engineering/contributing/SKILL.md +1 -1
- package/skills/engineering/contributing/release-check.mjs +3 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/e2e-test/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
- package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
- package/skills/engineering/ensure-coverage/SKILL.md +1 -1
- package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/golden-path/release-check.mjs +3 -1
- package/skills/engineering/harness-runtime/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/SKILL.md +11 -20
- package/skills/engineering/resolve-issues/references/delivery-progress.md +63 -0
- package/skills/engineering/resolve-issues/references/expire.md +24 -11
- package/skills/engineering/resolve-issues/references/implementation.md +9 -1
- package/skills/engineering/resolve-issues/references/preflight.md +7 -0
- package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
- package/skills/engineering/resolve-issues/references/task-plan.md +12 -5
- package/skills/engineering/resolve-issues/release-check.mjs +3 -1
- package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
- package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
- package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/resolve-release/release-check.mjs +3 -1
- package/skills/engineering/rush-issues/SKILL.md +5 -2
- package/skills/engineering/rush-issues/release-check.mjs +3 -1
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/rush-release/release-check.mjs +3 -1
- package/skills/engineering/test-quality/SKILL.md +1 -1
- package/skills/engineering/test-quality/release-check.mjs +3 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
- package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
- package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -172
- package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
- package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
- package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
- package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
- package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
- package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
- package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
- package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
- package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
- package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
- package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
- package/skills/content/image-gen/scripts/options.test.mjs +0 -245
- package/skills/content/xai-search/tests/smoke_test.sh +0 -36
- package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
- package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
- package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
- package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
- package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
- package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
- package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
- package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
- package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
- package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
- package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
- package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
- package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
- package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
- package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
- package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
- package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
- package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
- package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
- package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
- package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
- package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
- package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
- package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
- package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
- package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
- package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
- package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
- package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
- package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
- package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
- package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
- package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
- package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
- package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
- package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
- package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
- package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
- package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
- package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
- package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
- package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
- package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
- package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
- package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
- package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
- package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
- package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
- package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
- package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
- package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
- package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
- package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
- package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
- package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
- package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
- package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
- package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
- package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
- package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +0 -520
|
@@ -1,967 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { tmpdir } from 'node:os';
|
|
6
|
-
import { dirname, join, resolve } from 'node:path';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
CapabilityUnavailableError,
|
|
11
|
-
HarnessRuntimeError,
|
|
12
|
-
HARNESS_RUNTIME_ERROR_CODES,
|
|
13
|
-
QUESTION_ADAPTER_PRIORITY,
|
|
14
|
-
QUESTION_FALLBACK_POLICY,
|
|
15
|
-
UnsupportedQuestionError,
|
|
16
|
-
defineCurrentTurnCapabilities,
|
|
17
|
-
defineModelAvailability,
|
|
18
|
-
defineQuestionAnswers,
|
|
19
|
-
defineQuestionSpec,
|
|
20
|
-
normalizeQuestionResponses,
|
|
21
|
-
planStructuredQuestions,
|
|
22
|
-
} from './index.mjs';
|
|
23
|
-
|
|
24
|
-
const RUNTIME_DIR = dirname(fileURLToPath(import.meta.url));
|
|
25
|
-
const QUESTION_RESPONSE_FIXTURES = JSON.parse(readFileSync(
|
|
26
|
-
join(RUNTIME_DIR, 'fixtures', 'question-responses.json'),
|
|
27
|
-
'utf8',
|
|
28
|
-
));
|
|
29
|
-
const NATIVE_QUESTION_SCHEMAS = JSON.parse(readFileSync(
|
|
30
|
-
join(RUNTIME_DIR, 'fixtures', 'native-question-schemas.json'),
|
|
31
|
-
'utf8',
|
|
32
|
-
));
|
|
33
|
-
|
|
34
|
-
// @covers package:harness-runtime
|
|
35
|
-
// @level unit
|
|
36
|
-
test('current-turn capabilities preserve only explicitly advertised native tools and spawn models', () => {
|
|
37
|
-
const input = {
|
|
38
|
-
nativeTools: ['ask_user_question', 'functions'],
|
|
39
|
-
spawnModelIds: ['openai/gpt-a', 'anthropic/model-b'],
|
|
40
|
-
};
|
|
41
|
-
const capabilities = defineCurrentTurnCapabilities(input);
|
|
42
|
-
|
|
43
|
-
assert.deepEqual(capabilities, input);
|
|
44
|
-
assert.ok(Object.isFrozen(capabilities));
|
|
45
|
-
assert.ok(Object.isFrozen(capabilities.nativeTools));
|
|
46
|
-
assert.ok(Object.isFrozen(capabilities.spawnModelIds));
|
|
47
|
-
input.nativeTools.push('inferred-tool');
|
|
48
|
-
assert.deepEqual(capabilities.nativeTools, ['ask_user_question', 'functions']);
|
|
49
|
-
|
|
50
|
-
assert.throws(
|
|
51
|
-
() => defineCurrentTurnCapabilities({ nativeTools: ['same', 'same'], spawnModelIds: [] }),
|
|
52
|
-
(error) => error instanceof HarnessRuntimeError
|
|
53
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_CAPABILITIES,
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// @covers package:harness-runtime
|
|
58
|
-
// @level unit
|
|
59
|
-
test('neutral questions and answers retain stable ids without selecting a native adapter', () => {
|
|
60
|
-
const question = defineQuestionSpec({
|
|
61
|
-
id: 'delivery-mode',
|
|
62
|
-
header: 'Delivery',
|
|
63
|
-
prompt: 'How should this unit be delivered?',
|
|
64
|
-
options: [
|
|
65
|
-
{ id: 'queue', label: 'Merge queue', description: 'Use the protected queue.' },
|
|
66
|
-
{ id: 'direct', label: 'Direct PR', description: 'Leave the PR merge-ready.' },
|
|
67
|
-
],
|
|
68
|
-
recommendationOrder: ['queue', 'direct'],
|
|
69
|
-
multiSelect: false,
|
|
70
|
-
secret: false,
|
|
71
|
-
});
|
|
72
|
-
const answers = defineQuestionAnswers({ 'delivery-mode': ['queue'] });
|
|
73
|
-
|
|
74
|
-
assert.equal(question.id, 'delivery-mode');
|
|
75
|
-
assert.deepEqual(question.recommendationOrder, ['queue', 'direct']);
|
|
76
|
-
assert.deepEqual(answers, { 'delivery-mode': ['queue'] });
|
|
77
|
-
assert.ok(Object.isFrozen(question.options[0]));
|
|
78
|
-
assert.ok(Object.isFrozen(answers['delivery-mode']));
|
|
79
|
-
|
|
80
|
-
assert.throws(
|
|
81
|
-
() => defineQuestionSpec({
|
|
82
|
-
id: 'duplicate-options',
|
|
83
|
-
header: 'Duplicate',
|
|
84
|
-
prompt: 'Choose.',
|
|
85
|
-
options: [{ id: 'same', label: 'One' }, { id: 'same', label: 'Two' }],
|
|
86
|
-
}),
|
|
87
|
-
(error) => error instanceof HarnessRuntimeError
|
|
88
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION,
|
|
89
|
-
);
|
|
90
|
-
assert.throws(
|
|
91
|
-
() => defineQuestionAnswers({ 'delivery-mode': [] }),
|
|
92
|
-
(error) => error instanceof HarnessRuntimeError
|
|
93
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_ANSWERS,
|
|
94
|
-
);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// @covers package:harness-runtime
|
|
98
|
-
// @level unit
|
|
99
|
-
test('model availability states its harness and completeness without claiming static config is full', () => {
|
|
100
|
-
const availability = defineModelAvailability({
|
|
101
|
-
harness: 'codex',
|
|
102
|
-
completeness: 'partial',
|
|
103
|
-
models: [
|
|
104
|
-
{ id: 'openai/gpt-a', sources: ['current-turn'] },
|
|
105
|
-
{ id: 'openai/gpt-b', sources: ['credentials', 'config'] },
|
|
106
|
-
],
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
assert.deepEqual(availability, {
|
|
110
|
-
harness: 'codex',
|
|
111
|
-
completeness: 'partial',
|
|
112
|
-
models: [
|
|
113
|
-
{ id: 'openai/gpt-a', sources: ['current-turn'] },
|
|
114
|
-
{ id: 'openai/gpt-b', sources: ['credentials', 'config'] },
|
|
115
|
-
],
|
|
116
|
-
});
|
|
117
|
-
assert.ok(Object.isFrozen(availability.models[0].sources));
|
|
118
|
-
assert.throws(
|
|
119
|
-
() => defineModelAvailability({
|
|
120
|
-
harness: 'codex',
|
|
121
|
-
completeness: 'full',
|
|
122
|
-
models: [{ id: 'openai/gpt-a', sources: ['config'] }],
|
|
123
|
-
}),
|
|
124
|
-
(error) => error instanceof HarnessRuntimeError
|
|
125
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_MODEL_AVAILABILITY,
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// @covers package:harness-runtime
|
|
130
|
-
// @level unit
|
|
131
|
-
test('capability failures and fallback policy forbid textual multiple-choice emulation', () => {
|
|
132
|
-
assert.deepEqual(QUESTION_FALLBACK_POLICY, {
|
|
133
|
-
noNativeQuestionTool: {
|
|
134
|
-
freeText: 'concise-free-text-only',
|
|
135
|
-
multipleChoice: 'forbidden',
|
|
136
|
-
},
|
|
137
|
-
unsupportedMultiSelect: 'error',
|
|
138
|
-
secretInput: 'error',
|
|
139
|
-
});
|
|
140
|
-
assert.ok(Object.isFrozen(QUESTION_FALLBACK_POLICY.noNativeQuestionTool));
|
|
141
|
-
|
|
142
|
-
const unavailable = new CapabilityUnavailableError('native-question-tool', {
|
|
143
|
-
availableTools: ['functions'],
|
|
144
|
-
});
|
|
145
|
-
assert.equal(unavailable.code, HARNESS_RUNTIME_ERROR_CODES.CAPABILITY_UNAVAILABLE);
|
|
146
|
-
assert.equal(unavailable.capability, 'native-question-tool');
|
|
147
|
-
assert.deepEqual(unavailable.details.availableTools, ['functions']);
|
|
148
|
-
|
|
149
|
-
const unsupported = new UnsupportedQuestionError('secret-input', { questionId: 'token' });
|
|
150
|
-
assert.equal(unsupported.code, HARNESS_RUNTIME_ERROR_CODES.UNSUPPORTED_QUESTION);
|
|
151
|
-
assert.equal(unsupported.reason, 'secret-input');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
function plannerQuestion(id, overrides = {}) {
|
|
155
|
-
return {
|
|
156
|
-
id,
|
|
157
|
-
header: 'Delivery',
|
|
158
|
-
prompt: `How should ${id} proceed?`,
|
|
159
|
-
options: [
|
|
160
|
-
{ id: 'direct', label: 'Direct', description: 'Proceed directly.' },
|
|
161
|
-
{ id: 'queue', label: 'Queue', description: 'Use the merge queue.' },
|
|
162
|
-
],
|
|
163
|
-
recommendationOrder: ['queue', 'direct'],
|
|
164
|
-
multiSelect: false,
|
|
165
|
-
secret: false,
|
|
166
|
-
...overrides,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// @covers package:harness-runtime
|
|
171
|
-
// @level unit
|
|
172
|
-
test('Pi planning is capability-driven, ordered, validated, and deterministically batched', () => {
|
|
173
|
-
assert.deepEqual(QUESTION_ADAPTER_PRIORITY, [
|
|
174
|
-
'ask_user_question',
|
|
175
|
-
'request_user_input',
|
|
176
|
-
'AskUserQuestion',
|
|
177
|
-
]);
|
|
178
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
179
|
-
nativeTools: ['AskUserQuestion', 'request_user_input', 'functions', 'ask_user_question'],
|
|
180
|
-
spawnModelIds: [],
|
|
181
|
-
});
|
|
182
|
-
const questions = Array.from({ length: 5 }, (_, index) => plannerQuestion(`gate-${index + 1}`));
|
|
183
|
-
const plan = planStructuredQuestions(capabilities, questions);
|
|
184
|
-
|
|
185
|
-
assert.equal(plan.kind, 'native');
|
|
186
|
-
assert.equal(plan.adapter, 'pi');
|
|
187
|
-
assert.equal(plan.toolName, 'ask_user_question');
|
|
188
|
-
assert.equal(plan.capacity, 4);
|
|
189
|
-
assert.equal(plan.requiresMultipleCalls, true);
|
|
190
|
-
assert.deepEqual(plan.batching, {
|
|
191
|
-
required: true,
|
|
192
|
-
reason: 'adapter-capacity',
|
|
193
|
-
callCount: 2,
|
|
194
|
-
});
|
|
195
|
-
assert.deepEqual(plan.calls.map((call) => call.questionIds), [
|
|
196
|
-
['gate-1', 'gate-2', 'gate-3', 'gate-4'],
|
|
197
|
-
['gate-5'],
|
|
198
|
-
]);
|
|
199
|
-
assert.deepEqual(plan.calls[0].payload.questions[0], {
|
|
200
|
-
question: 'How should gate-1 proceed?',
|
|
201
|
-
header: 'Delivery',
|
|
202
|
-
options: [
|
|
203
|
-
{ label: 'Queue', description: 'Use the merge queue.' },
|
|
204
|
-
{ label: 'Direct', description: 'Proceed directly.' },
|
|
205
|
-
],
|
|
206
|
-
multiSelect: false,
|
|
207
|
-
});
|
|
208
|
-
assert.ok(Object.isFrozen(plan.calls[0].payload.questions[0].options[0]));
|
|
209
|
-
|
|
210
|
-
assert.throws(
|
|
211
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('same'), plannerQuestion('same')]),
|
|
212
|
-
(error) => error instanceof HarnessRuntimeError
|
|
213
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION
|
|
214
|
-
&& error.details.reason === 'duplicate-question-id',
|
|
215
|
-
);
|
|
216
|
-
assert.throws(
|
|
217
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('long-header', { header: 'x'.repeat(17) })]),
|
|
218
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
219
|
-
&& error.reason === 'header-too-long',
|
|
220
|
-
);
|
|
221
|
-
assert.throws(
|
|
222
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('long-label', {
|
|
223
|
-
options: [
|
|
224
|
-
{ id: 'long', label: 'x'.repeat(61), description: 'Too long.' },
|
|
225
|
-
{ id: 'short', label: 'Short', description: 'Short.' },
|
|
226
|
-
],
|
|
227
|
-
recommendationOrder: [],
|
|
228
|
-
})]),
|
|
229
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
230
|
-
&& error.reason === 'option-label-too-long',
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// @covers package:harness-runtime
|
|
235
|
-
// @level unit
|
|
236
|
-
test('Codex planning wins fixed priority and emits stable ids in deterministic groups of three', () => {
|
|
237
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
238
|
-
nativeTools: ['AskUserQuestion', 'request_user_input'],
|
|
239
|
-
spawnModelIds: [],
|
|
240
|
-
});
|
|
241
|
-
const questions = Array.from({ length: 4 }, (_, index) => plannerQuestion(`gate_${index + 1}`));
|
|
242
|
-
const plan = planStructuredQuestions(capabilities, questions);
|
|
243
|
-
|
|
244
|
-
assert.equal(plan.adapter, 'codex');
|
|
245
|
-
assert.equal(plan.toolName, 'request_user_input');
|
|
246
|
-
assert.equal(plan.capacity, 3);
|
|
247
|
-
assert.equal(plan.requiresMultipleCalls, true);
|
|
248
|
-
assert.deepEqual(plan.calls.map((call) => call.questionIds), [
|
|
249
|
-
['gate_1', 'gate_2', 'gate_3'],
|
|
250
|
-
['gate_4'],
|
|
251
|
-
]);
|
|
252
|
-
assert.deepEqual(plan.calls[0].payload.questions[0], {
|
|
253
|
-
id: 'gate_1',
|
|
254
|
-
header: 'Delivery',
|
|
255
|
-
question: 'How should gate_1 proceed?',
|
|
256
|
-
options: [
|
|
257
|
-
{ label: 'Queue (Recommended)', description: 'Use the merge queue.' },
|
|
258
|
-
{ label: 'Direct', description: 'Proceed directly.' },
|
|
259
|
-
],
|
|
260
|
-
});
|
|
261
|
-
assert.deepEqual(plan.calls[0].answerMappings[0].options, [
|
|
262
|
-
{ id: 'queue', label: 'Queue (Recommended)' },
|
|
263
|
-
{ id: 'direct', label: 'Direct' },
|
|
264
|
-
]);
|
|
265
|
-
assert.throws(
|
|
266
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('not-stable')]),
|
|
267
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
268
|
-
&& error.reason === 'invalid-question-id',
|
|
269
|
-
);
|
|
270
|
-
assert.throws(
|
|
271
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('multi', { multiSelect: true })]),
|
|
272
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
273
|
-
&& error.reason === 'multi-select',
|
|
274
|
-
);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// @covers package:harness-runtime
|
|
278
|
-
// @level unit
|
|
279
|
-
// @fails-without-fix package:harness-runtime
|
|
280
|
-
test('Codex rejects recommendation suffix collisions before emitting native labels', () => {
|
|
281
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
282
|
-
nativeTools: ['request_user_input'],
|
|
283
|
-
spawnModelIds: [],
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
assert.throws(
|
|
287
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('delivery', {
|
|
288
|
-
options: [
|
|
289
|
-
{ id: 'queue', label: 'Queue', description: 'Use the merge queue.' },
|
|
290
|
-
{
|
|
291
|
-
id: 'literal_suffix',
|
|
292
|
-
label: 'Queue (Recommended)',
|
|
293
|
-
description: 'Keep the literal suffix.',
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
recommendationOrder: ['queue'],
|
|
297
|
-
})]),
|
|
298
|
-
(error) => error instanceof HarnessRuntimeError
|
|
299
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION
|
|
300
|
-
&& error.details.reason === 'native-option-label-collision'
|
|
301
|
-
&& error.details.adapter === 'codex'
|
|
302
|
-
&& error.details.questionId === 'delivery'
|
|
303
|
-
&& error.details.label === 'Queue (Recommended)'
|
|
304
|
-
&& error.details.optionIds.join(',') === 'queue,literal_suffix',
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// @covers package:harness-runtime
|
|
309
|
-
// @level unit
|
|
310
|
-
// @fails-without-fix package:harness-runtime
|
|
311
|
-
test('Claude Code rejects multi-select answer-format collisions before emission', () => {
|
|
312
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
313
|
-
nativeTools: ['AskUserQuestion'],
|
|
314
|
-
spawnModelIds: [],
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
assert.throws(
|
|
318
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('features', {
|
|
319
|
-
multiSelect: true,
|
|
320
|
-
options: [
|
|
321
|
-
{ id: 'a', label: 'A', description: 'Select A.' },
|
|
322
|
-
{ id: 'b', label: 'B', description: 'Select B.' },
|
|
323
|
-
{ id: 'combined', label: 'A, B', description: 'Select the literal combined label.' },
|
|
324
|
-
],
|
|
325
|
-
recommendationOrder: [],
|
|
326
|
-
})]),
|
|
327
|
-
(error) => error instanceof HarnessRuntimeError
|
|
328
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION
|
|
329
|
-
&& error.details.reason === 'native-answer-format-collision'
|
|
330
|
-
&& error.details.adapter === 'claude-code'
|
|
331
|
-
&& error.details.questionId === 'features'
|
|
332
|
-
&& error.details.answer === 'A, B',
|
|
333
|
-
);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// @covers package:harness-runtime
|
|
337
|
-
// @level unit
|
|
338
|
-
// @fails-without-fix package:harness-runtime
|
|
339
|
-
test('normalization never guesses duplicate labels and preserves custom answers', () => {
|
|
340
|
-
const piPlan = planStructuredQuestions(
|
|
341
|
-
defineCurrentTurnCapabilities({ nativeTools: ['ask_user_question'], spawnModelIds: [] }),
|
|
342
|
-
[plannerQuestion('delivery', {
|
|
343
|
-
options: [
|
|
344
|
-
{ id: 'long', label: 'x'.repeat(60), description: 'Exact native maximum.' },
|
|
345
|
-
{ id: 'queue', label: 'Queue', description: 'Use the merge queue.' },
|
|
346
|
-
],
|
|
347
|
-
recommendationOrder: ['queue'],
|
|
348
|
-
})],
|
|
349
|
-
);
|
|
350
|
-
assert.equal(piPlan.calls[0].payload.questions[0].options[1].label.length, 60);
|
|
351
|
-
assert.deepEqual(normalizeQuestionResponses(piPlan, [{
|
|
352
|
-
details: {
|
|
353
|
-
answers: [{
|
|
354
|
-
questionIndex: 0,
|
|
355
|
-
question: 'How should delivery proceed?',
|
|
356
|
-
kind: 'custom',
|
|
357
|
-
answer: 'Queue',
|
|
358
|
-
}],
|
|
359
|
-
cancelled: false,
|
|
360
|
-
},
|
|
361
|
-
}]), { delivery: ['Queue'] });
|
|
362
|
-
|
|
363
|
-
for (const nativeTool of ['request_user_input', 'AskUserQuestion']) {
|
|
364
|
-
const plan = planStructuredQuestions(
|
|
365
|
-
defineCurrentTurnCapabilities({ nativeTools: [nativeTool], spawnModelIds: [] }),
|
|
366
|
-
[plannerQuestion('delivery')],
|
|
367
|
-
);
|
|
368
|
-
const response = nativeTool === 'request_user_input'
|
|
369
|
-
? { answers: { delivery: { answers: ['Ship after lunch'] } } }
|
|
370
|
-
: { answers: { 'How should delivery proceed?': 'Ship after lunch' } };
|
|
371
|
-
assert.deepEqual(normalizeQuestionResponses(plan, [response]), {
|
|
372
|
-
delivery: ['Ship after lunch'],
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const malformedPlan = structuredClone(planStructuredQuestions(
|
|
377
|
-
defineCurrentTurnCapabilities({ nativeTools: ['request_user_input'], spawnModelIds: [] }),
|
|
378
|
-
[plannerQuestion('delivery')],
|
|
379
|
-
));
|
|
380
|
-
malformedPlan.calls[0].answerMappings[0].options[1].label = 'Queue (Recommended)';
|
|
381
|
-
assert.throws(
|
|
382
|
-
() => normalizeQuestionResponses(malformedPlan, [{
|
|
383
|
-
answers: { delivery: { answers: ['Queue (Recommended)'] } },
|
|
384
|
-
}]),
|
|
385
|
-
(error) => error instanceof HarnessRuntimeError
|
|
386
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_ANSWERS
|
|
387
|
-
&& error.details.reason === 'ambiguous-option-label'
|
|
388
|
-
&& error.details.questionId === 'delivery'
|
|
389
|
-
&& error.details.optionIds.join(',') === 'queue,direct',
|
|
390
|
-
);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// @covers package:harness-runtime
|
|
394
|
-
// @level unit
|
|
395
|
-
test('Claude Code planning uses native field names and groups four questions per call', () => {
|
|
396
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
397
|
-
nativeTools: ['AskUserQuestion'],
|
|
398
|
-
spawnModelIds: [],
|
|
399
|
-
});
|
|
400
|
-
const questions = Array.from({ length: 5 }, (_, index) => plannerQuestion(`gate-${index + 1}`, {
|
|
401
|
-
multiSelect: index === 0,
|
|
402
|
-
}));
|
|
403
|
-
const plan = planStructuredQuestions(capabilities, questions);
|
|
404
|
-
|
|
405
|
-
assert.equal(plan.adapter, 'claude-code');
|
|
406
|
-
assert.equal(plan.toolName, 'AskUserQuestion');
|
|
407
|
-
assert.equal(plan.capacity, 4);
|
|
408
|
-
assert.equal(plan.requiresMultipleCalls, true);
|
|
409
|
-
assert.deepEqual(plan.calls.map((call) => call.questionIds), [
|
|
410
|
-
['gate-1', 'gate-2', 'gate-3', 'gate-4'],
|
|
411
|
-
['gate-5'],
|
|
412
|
-
]);
|
|
413
|
-
assert.deepEqual(plan.calls[0].payload.questions[0], {
|
|
414
|
-
question: 'How should gate-1 proceed?',
|
|
415
|
-
header: 'Delivery',
|
|
416
|
-
options: [
|
|
417
|
-
{ label: 'Queue', description: 'Use the merge queue.' },
|
|
418
|
-
{ label: 'Direct', description: 'Proceed directly.' },
|
|
419
|
-
],
|
|
420
|
-
multiSelect: true,
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// @covers package:harness-runtime
|
|
425
|
-
// @level unit
|
|
426
|
-
test('no-adapter planning distinguishes concise free text from forbidden textual choices', () => {
|
|
427
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
428
|
-
nativeTools: ['functions'],
|
|
429
|
-
spawnModelIds: [],
|
|
430
|
-
});
|
|
431
|
-
const freeText = planStructuredQuestions(capabilities, [plannerQuestion('context', {
|
|
432
|
-
prompt: 'What context is missing?',
|
|
433
|
-
options: [],
|
|
434
|
-
recommendationOrder: [],
|
|
435
|
-
})]);
|
|
436
|
-
assert.deepEqual(freeText, {
|
|
437
|
-
kind: 'fallback',
|
|
438
|
-
adapter: null,
|
|
439
|
-
reason: 'no-supported-native-question-tool',
|
|
440
|
-
decision: 'concise-free-text',
|
|
441
|
-
freeText: {
|
|
442
|
-
allowed: true,
|
|
443
|
-
questionId: 'context',
|
|
444
|
-
prompt: 'What context is missing?',
|
|
445
|
-
},
|
|
446
|
-
textualMultipleChoice: {
|
|
447
|
-
allowed: false,
|
|
448
|
-
reason: 'forbidden',
|
|
449
|
-
},
|
|
450
|
-
availableTools: ['functions'],
|
|
451
|
-
});
|
|
452
|
-
assert.ok(Object.isFrozen(freeText.freeText));
|
|
453
|
-
|
|
454
|
-
const forbidden = planStructuredQuestions(capabilities, [plannerQuestion('delivery')]);
|
|
455
|
-
assert.equal(forbidden.kind, 'fallback');
|
|
456
|
-
assert.equal(forbidden.decision, 'textual-multiple-choice-forbidden');
|
|
457
|
-
assert.deepEqual(forbidden.freeText, {
|
|
458
|
-
allowed: false,
|
|
459
|
-
reason: 'logical-question-has-options',
|
|
460
|
-
});
|
|
461
|
-
const multipleFreeText = planStructuredQuestions(capabilities, [
|
|
462
|
-
plannerQuestion('first_context', { options: [], recommendationOrder: [] }),
|
|
463
|
-
plannerQuestion('second_context', { options: [], recommendationOrder: [] }),
|
|
464
|
-
]);
|
|
465
|
-
assert.deepEqual(multipleFreeText.freeText, {
|
|
466
|
-
allowed: false,
|
|
467
|
-
reason: 'multiple-questions',
|
|
468
|
-
});
|
|
469
|
-
assert.deepEqual(forbidden.textualMultipleChoice, {
|
|
470
|
-
allowed: false,
|
|
471
|
-
reason: 'forbidden',
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
assert.throws(
|
|
475
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('token', {
|
|
476
|
-
options: [],
|
|
477
|
-
recommendationOrder: [],
|
|
478
|
-
secret: true,
|
|
479
|
-
})]),
|
|
480
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
481
|
-
&& error.reason === 'secret-input',
|
|
482
|
-
);
|
|
483
|
-
assert.throws(
|
|
484
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('features', {
|
|
485
|
-
options: [],
|
|
486
|
-
recommendationOrder: [],
|
|
487
|
-
multiSelect: true,
|
|
488
|
-
})]),
|
|
489
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
490
|
-
&& error.reason === 'multi-select',
|
|
491
|
-
);
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// @covers package:harness-runtime
|
|
495
|
-
// @level unit
|
|
496
|
-
test('native adapters enforce exact capacities, field limits, and option invariants', () => {
|
|
497
|
-
const adapters = [
|
|
498
|
-
{ tool: 'ask_user_question', capacity: 4, headerLimit: 16, optionLimit: 4 },
|
|
499
|
-
{ tool: 'request_user_input', capacity: 3, headerLimit: 12, optionLimit: 3 },
|
|
500
|
-
{ tool: 'AskUserQuestion', capacity: 4, headerLimit: 12, optionLimit: 4 },
|
|
501
|
-
];
|
|
502
|
-
for (const { tool, capacity, headerLimit, optionLimit } of adapters) {
|
|
503
|
-
const capabilities = defineCurrentTurnCapabilities({ nativeTools: [tool], spawnModelIds: [] });
|
|
504
|
-
const exactCapacity = Array.from({ length: capacity }, (_, index) =>
|
|
505
|
-
plannerQuestion(`gate_${index + 1}`, { header: 'x'.repeat(headerLimit) }));
|
|
506
|
-
const plan = planStructuredQuestions(capabilities, exactCapacity);
|
|
507
|
-
assert.deepEqual(plan.batching, { required: false, reason: null, callCount: 1 });
|
|
508
|
-
|
|
509
|
-
const maximumOptions = Array.from({ length: optionLimit }, (_, index) => ({
|
|
510
|
-
id: `choice_${index + 1}`,
|
|
511
|
-
label: `Choice ${index + 1}`,
|
|
512
|
-
description: `Choose ${index + 1}.`,
|
|
513
|
-
}));
|
|
514
|
-
assert.equal(planStructuredQuestions(capabilities, [plannerQuestion('options', {
|
|
515
|
-
options: maximumOptions,
|
|
516
|
-
recommendationOrder: [],
|
|
517
|
-
})]).kind, 'native');
|
|
518
|
-
assert.throws(
|
|
519
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('too_many', {
|
|
520
|
-
options: [...maximumOptions, {
|
|
521
|
-
id: 'overflow',
|
|
522
|
-
label: 'Overflow',
|
|
523
|
-
description: 'Exceeds the native limit.',
|
|
524
|
-
}],
|
|
525
|
-
recommendationOrder: [],
|
|
526
|
-
})]),
|
|
527
|
-
(error) => error instanceof UnsupportedQuestionError && error.reason === 'option-count',
|
|
528
|
-
);
|
|
529
|
-
assert.throws(
|
|
530
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('long_header', {
|
|
531
|
-
header: 'x'.repeat(headerLimit + 1),
|
|
532
|
-
})]),
|
|
533
|
-
(error) => error instanceof UnsupportedQuestionError && error.reason === 'header-too-long',
|
|
534
|
-
);
|
|
535
|
-
assert.throws(
|
|
536
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('duplicate_label', {
|
|
537
|
-
options: [
|
|
538
|
-
{ id: 'first', label: 'Same', description: 'First.' },
|
|
539
|
-
{ id: 'second', label: 'Same', description: 'Second.' },
|
|
540
|
-
],
|
|
541
|
-
recommendationOrder: [],
|
|
542
|
-
})]),
|
|
543
|
-
(error) => error instanceof HarnessRuntimeError
|
|
544
|
-
&& error.details.reason === 'duplicate-option-label',
|
|
545
|
-
);
|
|
546
|
-
assert.throws(
|
|
547
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('description', {
|
|
548
|
-
options: [
|
|
549
|
-
{ id: 'first', label: 'First' },
|
|
550
|
-
{ id: 'second', label: 'Second', description: 'Second.' },
|
|
551
|
-
],
|
|
552
|
-
recommendationOrder: [],
|
|
553
|
-
})]),
|
|
554
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
555
|
-
&& error.reason === 'option-description-required',
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
|
|
560
|
-
// @covers package:harness-runtime
|
|
561
|
-
// @level unit
|
|
562
|
-
test('malformed neutral specs and unsupported native inputs fail before invocation', () => {
|
|
563
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
564
|
-
nativeTools: ['ask_user_question'],
|
|
565
|
-
spawnModelIds: [],
|
|
566
|
-
});
|
|
567
|
-
for (const questions of [null, []]) {
|
|
568
|
-
assert.throws(
|
|
569
|
-
() => planStructuredQuestions(capabilities, questions),
|
|
570
|
-
(error) => error instanceof HarnessRuntimeError
|
|
571
|
-
&& error.details.reason === 'empty-question-batch',
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
assert.throws(
|
|
575
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('duplicate_options', {
|
|
576
|
-
options: [
|
|
577
|
-
{ id: 'same', label: 'First', description: 'First.' },
|
|
578
|
-
{ id: 'same', label: 'Second', description: 'Second.' },
|
|
579
|
-
],
|
|
580
|
-
recommendationOrder: [],
|
|
581
|
-
})]),
|
|
582
|
-
(error) => error instanceof HarnessRuntimeError
|
|
583
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION,
|
|
584
|
-
);
|
|
585
|
-
assert.throws(
|
|
586
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('recommendation', {
|
|
587
|
-
recommendationOrder: ['missing'],
|
|
588
|
-
})]),
|
|
589
|
-
(error) => error instanceof HarnessRuntimeError
|
|
590
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION,
|
|
591
|
-
);
|
|
592
|
-
assert.throws(
|
|
593
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion('boolean', {
|
|
594
|
-
multiSelect: 'yes',
|
|
595
|
-
})]),
|
|
596
|
-
(error) => error instanceof HarnessRuntimeError
|
|
597
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION,
|
|
598
|
-
);
|
|
599
|
-
for (const nativeTool of QUESTION_ADAPTER_PRIORITY) {
|
|
600
|
-
const nativeCapabilities = defineCurrentTurnCapabilities({
|
|
601
|
-
nativeTools: [nativeTool],
|
|
602
|
-
spawnModelIds: [],
|
|
603
|
-
});
|
|
604
|
-
assert.throws(
|
|
605
|
-
() => planStructuredQuestions(nativeCapabilities, [plannerQuestion('empty', {
|
|
606
|
-
options: [],
|
|
607
|
-
recommendationOrder: [],
|
|
608
|
-
})]),
|
|
609
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
610
|
-
&& error.reason === 'option-count',
|
|
611
|
-
);
|
|
612
|
-
assert.throws(
|
|
613
|
-
() => planStructuredQuestions(nativeCapabilities, [plannerQuestion('secret', { secret: true })]),
|
|
614
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
615
|
-
&& error.reason === 'secret-input',
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
// @covers package:harness-runtime
|
|
621
|
-
// @level unit
|
|
622
|
-
test('all native response fixtures normalize selected labels and custom text to neutral ids', () => {
|
|
623
|
-
const adapters = [
|
|
624
|
-
{ adapter: 'pi', nativeTool: 'ask_user_question' },
|
|
625
|
-
{ adapter: 'codex', nativeTool: 'request_user_input' },
|
|
626
|
-
{ adapter: 'claude-code', nativeTool: 'AskUserQuestion' },
|
|
627
|
-
];
|
|
628
|
-
for (const { adapter, nativeTool } of adapters) {
|
|
629
|
-
const plan = planStructuredQuestions(
|
|
630
|
-
defineCurrentTurnCapabilities({ nativeTools: [nativeTool], spawnModelIds: [] }),
|
|
631
|
-
[plannerQuestion('delivery'), plannerQuestion('context')],
|
|
632
|
-
);
|
|
633
|
-
const answers = normalizeQuestionResponses(plan, [QUESTION_RESPONSE_FIXTURES[adapter].success]);
|
|
634
|
-
assert.deepEqual(answers, {
|
|
635
|
-
delivery: ['queue'],
|
|
636
|
-
context: ['After lunch'],
|
|
637
|
-
});
|
|
638
|
-
assert.ok(Object.isFrozen(answers));
|
|
639
|
-
assert.ok(Object.isFrozen(answers.delivery));
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
// @covers package:harness-runtime
|
|
644
|
-
// @level unit
|
|
645
|
-
test('Pi and Claude multi-select responses normalize to the same option-id arrays', () => {
|
|
646
|
-
for (const [adapter, nativeTool] of [
|
|
647
|
-
['pi', 'ask_user_question'],
|
|
648
|
-
['claude-code', 'AskUserQuestion'],
|
|
649
|
-
]) {
|
|
650
|
-
const plan = planStructuredQuestions(
|
|
651
|
-
defineCurrentTurnCapabilities({ nativeTools: [nativeTool], spawnModelIds: [] }),
|
|
652
|
-
[plannerQuestion('features', { multiSelect: true })],
|
|
653
|
-
);
|
|
654
|
-
assert.deepEqual(
|
|
655
|
-
normalizeQuestionResponses(plan, [QUESTION_RESPONSE_FIXTURES[adapter].multi]),
|
|
656
|
-
{ features: ['queue', 'direct'] },
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
// @covers package:harness-runtime
|
|
662
|
-
// @level unit
|
|
663
|
-
test('cancellation discards partial gates and malformed native responses fail closed', () => {
|
|
664
|
-
for (const [adapter, nativeTool] of [
|
|
665
|
-
['pi', 'ask_user_question'],
|
|
666
|
-
['codex', 'request_user_input'],
|
|
667
|
-
['claude-code', 'AskUserQuestion'],
|
|
668
|
-
]) {
|
|
669
|
-
const plan = planStructuredQuestions(
|
|
670
|
-
defineCurrentTurnCapabilities({ nativeTools: [nativeTool], spawnModelIds: [] }),
|
|
671
|
-
[plannerQuestion('delivery')],
|
|
672
|
-
);
|
|
673
|
-
assert.deepEqual(
|
|
674
|
-
normalizeQuestionResponses(plan, [QUESTION_RESPONSE_FIXTURES[adapter].cancelled]),
|
|
675
|
-
{},
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const batchedPiPlan = planStructuredQuestions(
|
|
680
|
-
defineCurrentTurnCapabilities({ nativeTools: ['ask_user_question'], spawnModelIds: [] }),
|
|
681
|
-
Array.from({ length: 5 }, (_, index) => plannerQuestion(`gate-${index + 1}`)),
|
|
682
|
-
);
|
|
683
|
-
const completedFirstCall = {
|
|
684
|
-
details: {
|
|
685
|
-
answers: batchedPiPlan.calls[0].answerMappings.map((mapping, questionIndex) => ({
|
|
686
|
-
questionIndex,
|
|
687
|
-
question: mapping.prompt,
|
|
688
|
-
kind: 'option',
|
|
689
|
-
answer: 'Queue',
|
|
690
|
-
})),
|
|
691
|
-
cancelled: false,
|
|
692
|
-
},
|
|
693
|
-
};
|
|
694
|
-
assert.deepEqual(normalizeQuestionResponses(batchedPiPlan, [
|
|
695
|
-
completedFirstCall,
|
|
696
|
-
QUESTION_RESPONSE_FIXTURES.pi.cancelled,
|
|
697
|
-
]), {});
|
|
698
|
-
|
|
699
|
-
const codexPlan = planStructuredQuestions(
|
|
700
|
-
defineCurrentTurnCapabilities({ nativeTools: ['request_user_input'], spawnModelIds: [] }),
|
|
701
|
-
[plannerQuestion('delivery')],
|
|
702
|
-
);
|
|
703
|
-
assert.throws(
|
|
704
|
-
() => normalizeQuestionResponses(codexPlan, [{
|
|
705
|
-
answers: { unknown: { answers: ['Queue'] } },
|
|
706
|
-
}]),
|
|
707
|
-
(error) => error instanceof HarnessRuntimeError
|
|
708
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_ANSWERS,
|
|
709
|
-
);
|
|
710
|
-
assert.throws(
|
|
711
|
-
() => normalizeQuestionResponses(codexPlan, [{
|
|
712
|
-
answers: { delivery: { answers: [] } },
|
|
713
|
-
}]),
|
|
714
|
-
(error) => error instanceof HarnessRuntimeError
|
|
715
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_ANSWERS,
|
|
716
|
-
);
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
// @covers package:harness-runtime
|
|
720
|
-
// @level unit
|
|
721
|
-
test('malformed and incomplete native responses fail closed', () => {
|
|
722
|
-
const plans = Object.fromEntries([
|
|
723
|
-
['pi', 'ask_user_question'],
|
|
724
|
-
['codex', 'request_user_input'],
|
|
725
|
-
['claude-code', 'AskUserQuestion'],
|
|
726
|
-
].map(([adapter, tool]) => [adapter, planStructuredQuestions(
|
|
727
|
-
defineCurrentTurnCapabilities({ nativeTools: [tool], spawnModelIds: [] }),
|
|
728
|
-
[plannerQuestion('delivery'), plannerQuestion('context')],
|
|
729
|
-
)]));
|
|
730
|
-
|
|
731
|
-
assert.throws(
|
|
732
|
-
() => normalizeQuestionResponses({}, []),
|
|
733
|
-
(error) => error instanceof HarnessRuntimeError
|
|
734
|
-
&& error.details.reason === 'invalid-question-plan',
|
|
735
|
-
);
|
|
736
|
-
for (const responses of [null, []]) {
|
|
737
|
-
assert.throws(
|
|
738
|
-
() => normalizeQuestionResponses(plans.pi, responses),
|
|
739
|
-
(error) => error instanceof HarnessRuntimeError
|
|
740
|
-
&& error.details.reason === 'response-count',
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
const malformedResponses = [
|
|
744
|
-
[plans.pi, {}],
|
|
745
|
-
[plans.pi, { details: { answers: [
|
|
746
|
-
{
|
|
747
|
-
questionIndex: 0,
|
|
748
|
-
question: 'How should delivery proceed?',
|
|
749
|
-
kind: 'option',
|
|
750
|
-
answer: 'Queue',
|
|
751
|
-
},
|
|
752
|
-
{
|
|
753
|
-
questionIndex: 0,
|
|
754
|
-
question: 'How should delivery proceed?',
|
|
755
|
-
kind: 'option',
|
|
756
|
-
answer: 'Queue',
|
|
757
|
-
},
|
|
758
|
-
], cancelled: false } }],
|
|
759
|
-
[plans.pi, { details: { answers: [{
|
|
760
|
-
questionIndex: 0,
|
|
761
|
-
question: 'How should delivery proceed?',
|
|
762
|
-
kind: 'multi',
|
|
763
|
-
selected: ['Queue'],
|
|
764
|
-
}], cancelled: false } }],
|
|
765
|
-
[plans.pi, { details: { answers: [{
|
|
766
|
-
questionIndex: 0,
|
|
767
|
-
question: 'How should delivery proceed?',
|
|
768
|
-
kind: 'option',
|
|
769
|
-
answer: 'Missing option',
|
|
770
|
-
}], cancelled: false } }],
|
|
771
|
-
[plans.pi, { details: { answers: [{
|
|
772
|
-
questionIndex: 0,
|
|
773
|
-
question: 'How should delivery proceed?',
|
|
774
|
-
kind: 'custom',
|
|
775
|
-
answer: '',
|
|
776
|
-
}], cancelled: false } }],
|
|
777
|
-
[plans.pi, { details: { answers: [{
|
|
778
|
-
questionIndex: 0,
|
|
779
|
-
question: 'Wrong prompt',
|
|
780
|
-
kind: 'option',
|
|
781
|
-
answer: 'Queue',
|
|
782
|
-
}], cancelled: false } }],
|
|
783
|
-
[plans.pi, { details: { answers: [{
|
|
784
|
-
questionIndex: 0,
|
|
785
|
-
question: 'How should delivery proceed?',
|
|
786
|
-
kind: 'unknown',
|
|
787
|
-
answer: 'Queue',
|
|
788
|
-
}], cancelled: false } }],
|
|
789
|
-
[plans.codex, {}],
|
|
790
|
-
[plans.codex, { answers: { delivery: { answers: [''] } } }],
|
|
791
|
-
[plans.codex, { answers: { delivery: { answers: ['Queue (Recommended)', 'Direct'] } } }],
|
|
792
|
-
[plans['claude-code'], {}],
|
|
793
|
-
[plans['claude-code'], { answers: { 'Unknown prompt': 'Queue' } }],
|
|
794
|
-
];
|
|
795
|
-
for (const [plan, response] of malformedResponses) {
|
|
796
|
-
assert.throws(
|
|
797
|
-
() => normalizeQuestionResponses(plan, [response]),
|
|
798
|
-
(error) => error instanceof HarnessRuntimeError
|
|
799
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_ANSWERS,
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
const partialResponses = [
|
|
804
|
-
[plans.pi, { details: { answers: [{
|
|
805
|
-
questionIndex: 0,
|
|
806
|
-
question: 'How should delivery proceed?',
|
|
807
|
-
kind: 'option',
|
|
808
|
-
answer: 'Queue',
|
|
809
|
-
}], cancelled: false } }],
|
|
810
|
-
[plans.codex, { answers: { delivery: { answers: ['Queue'] } } }],
|
|
811
|
-
[plans['claude-code'], { answers: { 'How should delivery proceed?': 'Queue' } }],
|
|
812
|
-
];
|
|
813
|
-
for (const [plan, response] of partialResponses) {
|
|
814
|
-
assert.throws(
|
|
815
|
-
() => normalizeQuestionResponses(plan, [response]),
|
|
816
|
-
(error) => error instanceof HarnessRuntimeError
|
|
817
|
-
&& error.details.reason === 'incomplete-native-response',
|
|
818
|
-
);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
assert.throws(
|
|
822
|
-
() => planStructuredQuestions(
|
|
823
|
-
defineCurrentTurnCapabilities({ nativeTools: ['AskUserQuestion'], spawnModelIds: [] }),
|
|
824
|
-
[plannerQuestion('ambiguous', {
|
|
825
|
-
multiSelect: true,
|
|
826
|
-
options: [
|
|
827
|
-
{ id: 'a', label: 'A', description: 'A.' },
|
|
828
|
-
{ id: 'b', label: 'B', description: 'B.' },
|
|
829
|
-
{ id: 'combined', label: 'A, B', description: 'Combined.' },
|
|
830
|
-
],
|
|
831
|
-
recommendationOrder: [],
|
|
832
|
-
})],
|
|
833
|
-
),
|
|
834
|
-
(error) => error instanceof HarnessRuntimeError
|
|
835
|
-
&& error.details.reason === 'native-answer-format-collision',
|
|
836
|
-
);
|
|
837
|
-
|
|
838
|
-
const duplicatePlan = structuredClone(planStructuredQuestions(
|
|
839
|
-
defineCurrentTurnCapabilities({ nativeTools: ['ask_user_question'], spawnModelIds: [] }),
|
|
840
|
-
Array.from({ length: 5 }, (_, index) => plannerQuestion(`gate-${index + 1}`)),
|
|
841
|
-
));
|
|
842
|
-
duplicatePlan.calls[1].answerMappings[0].questionId = 'gate-1';
|
|
843
|
-
const duplicateResponses = duplicatePlan.calls.map((call) => ({
|
|
844
|
-
details: {
|
|
845
|
-
answers: call.answerMappings.map((mapping, questionIndex) => ({
|
|
846
|
-
questionIndex,
|
|
847
|
-
question: mapping.prompt,
|
|
848
|
-
kind: 'option',
|
|
849
|
-
answer: 'Queue',
|
|
850
|
-
})),
|
|
851
|
-
cancelled: false,
|
|
852
|
-
},
|
|
853
|
-
}));
|
|
854
|
-
assert.throws(
|
|
855
|
-
() => normalizeQuestionResponses(duplicatePlan, duplicateResponses),
|
|
856
|
-
(error) => error instanceof HarnessRuntimeError
|
|
857
|
-
&& error.details.reason === 'duplicate-question-answer',
|
|
858
|
-
);
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// @covers package:harness-runtime
|
|
862
|
-
// @level unit
|
|
863
|
-
test('malformed prompt and Pi sentinel collisions are rejected before native invocation', () => {
|
|
864
|
-
const capabilities = defineCurrentTurnCapabilities({
|
|
865
|
-
nativeTools: ['ask_user_question'],
|
|
866
|
-
spawnModelIds: [],
|
|
867
|
-
});
|
|
868
|
-
assert.throws(
|
|
869
|
-
() => planStructuredQuestions(capabilities, [
|
|
870
|
-
plannerQuestion('first', { prompt: 'Same prompt?' }),
|
|
871
|
-
plannerQuestion('second', { prompt: 'Same prompt?' }),
|
|
872
|
-
]),
|
|
873
|
-
(error) => error instanceof HarnessRuntimeError
|
|
874
|
-
&& error.code === HARNESS_RUNTIME_ERROR_CODES.INVALID_QUESTION
|
|
875
|
-
&& error.details.reason === 'duplicate-question-prompt',
|
|
876
|
-
);
|
|
877
|
-
for (const [index, label] of NATIVE_QUESTION_SCHEMAS.pi.reservedLabels.entries()) {
|
|
878
|
-
assert.throws(
|
|
879
|
-
() => planStructuredQuestions(capabilities, [plannerQuestion(`reserved-${index}`, {
|
|
880
|
-
options: [
|
|
881
|
-
{ id: 'reserved', label, description: 'Conflicts with native UI.' },
|
|
882
|
-
{ id: 'direct', label: 'Direct', description: 'Proceed directly.' },
|
|
883
|
-
],
|
|
884
|
-
recommendationOrder: [],
|
|
885
|
-
})]),
|
|
886
|
-
(error) => error instanceof UnsupportedQuestionError
|
|
887
|
-
&& error.reason === 'reserved-option-label',
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
// @covers package:harness-runtime
|
|
893
|
-
// @level integration
|
|
894
|
-
test('pinned native schemas and Codex operator docs separate configuration from capability', () => {
|
|
895
|
-
assert.equal(NATIVE_QUESTION_SCHEMAS.pi.questions.maximum, 4);
|
|
896
|
-
assert.equal(NATIVE_QUESTION_SCHEMAS.codex.questions.maximum, 3);
|
|
897
|
-
assert.equal(NATIVE_QUESTION_SCHEMAS.codex.recommendationLabelSuffix, ' (Recommended)');
|
|
898
|
-
assert.equal(NATIVE_QUESTION_SCHEMAS['claude-code'].questions.maximum, 4);
|
|
899
|
-
const contracts = readFileSync(join(RUNTIME_DIR, 'native-question-contracts.md'), 'utf8');
|
|
900
|
-
const codexDocs = readFileSync(join(RUNTIME_DIR, '..', '..', '..', 'agents', 'systems', 'codex.md'), 'utf8');
|
|
901
|
-
assert.match(contracts, /current-turn nativeTools/);
|
|
902
|
-
assert.match(contracts, /secret input/i);
|
|
903
|
-
assert.match(contracts, /textual multiple-choice/i);
|
|
904
|
-
for (const label of NATIVE_QUESTION_SCHEMAS.pi.reservedLabels) assert.match(contracts, new RegExp(label.replace('.', '\\.')));
|
|
905
|
-
for (const schema of Object.values(NATIVE_QUESTION_SCHEMAS)) assert.match(contracts, new RegExp(schema.toolName));
|
|
906
|
-
|
|
907
|
-
const packageContracts = readFileSync(join(RUNTIME_DIR, 'contracts.md'), 'utf8');
|
|
908
|
-
for (const name of [
|
|
909
|
-
'defineCurrentTurnCapabilities',
|
|
910
|
-
'defineQuestionSpec',
|
|
911
|
-
'defineQuestionAnswers',
|
|
912
|
-
'defineModelAvailability',
|
|
913
|
-
'planStructuredQuestions',
|
|
914
|
-
'normalizeQuestionResponses',
|
|
915
|
-
'discover-models.mjs',
|
|
916
|
-
'model-catalog.mjs',
|
|
917
|
-
]) assert.match(packageContracts, new RegExp(name.replace('.', '\\.')));
|
|
918
|
-
|
|
919
|
-
const catalogPath = join(RUNTIME_DIR, 'references', 'model-catalog.md');
|
|
920
|
-
const catalog = readFileSync(catalogPath, 'utf8');
|
|
921
|
-
for (const match of catalog.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
|
|
922
|
-
if (/^(?:https?:|#)/.test(match[1])) continue;
|
|
923
|
-
assert.ok(existsSync(resolve(dirname(catalogPath), match[1])), `broken model-catalog.md link: ${match[1]}`);
|
|
924
|
-
}
|
|
925
|
-
assert.doesNotMatch(catalog, /SKILL\.md pre-flight|\[pre-flight\.md\]\(pre-flight\.md\)/);
|
|
926
|
-
|
|
927
|
-
assert.match(codexDocs, /default_mode_request_user_input = true/);
|
|
928
|
-
assert.match(codexDocs, /does not prove.*available/i);
|
|
929
|
-
assert.match(codexDocs, /current-turn tool list/i);
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
function runConsumer(script) {
|
|
933
|
-
return spawnSync(process.execPath, [script], { encoding: 'utf8' });
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// @covers package:harness-runtime
|
|
937
|
-
// @level integration
|
|
938
|
-
test('the same consumer specifier resolves harness-runtime in nested source and flat installs', () => {
|
|
939
|
-
const root = mkdtempSync(join(tmpdir(), 'harness-runtime-resolution-'));
|
|
940
|
-
try {
|
|
941
|
-
const layouts = [
|
|
942
|
-
{
|
|
943
|
-
runtime: join(root, 'source', 'skills', 'engineering', 'harness-runtime'),
|
|
944
|
-
consumer: join(root, 'source', 'skills', 'engineering', 'resolve-issues', 'scripts', 'consumer.mjs'),
|
|
945
|
-
},
|
|
946
|
-
{
|
|
947
|
-
runtime: join(root, 'flat', 'harness-runtime'),
|
|
948
|
-
consumer: join(root, 'flat', 'resolve-issues', 'scripts', 'consumer.mjs'),
|
|
949
|
-
},
|
|
950
|
-
];
|
|
951
|
-
for (const layout of layouts) {
|
|
952
|
-
cpSync(RUNTIME_DIR, layout.runtime, { recursive: true });
|
|
953
|
-
mkdirSync(dirname(layout.consumer), { recursive: true });
|
|
954
|
-
writeFileSync(layout.consumer, [
|
|
955
|
-
"import { defineCurrentTurnCapabilities } from '../../harness-runtime/index.mjs';",
|
|
956
|
-
"const value = defineCurrentTurnCapabilities({ nativeTools: ['native'], spawnModelIds: ['model'] });",
|
|
957
|
-
'process.stdout.write(JSON.stringify(value));',
|
|
958
|
-
'',
|
|
959
|
-
].join('\n'));
|
|
960
|
-
const result = runConsumer(layout.consumer);
|
|
961
|
-
assert.equal(result.status, 0, result.stderr);
|
|
962
|
-
assert.deepEqual(JSON.parse(result.stdout), { nativeTools: ['native'], spawnModelIds: ['model'] });
|
|
963
|
-
}
|
|
964
|
-
} finally {
|
|
965
|
-
rmSync(root, { recursive: true, force: true });
|
|
966
|
-
}
|
|
967
|
-
});
|