@1aboveio/skills 1.0.8 → 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.
Files changed (214) hide show
  1. package/.codex-plugin/plugin.json +13 -0
  2. package/.codex-plugin/resources.json +1827 -0
  3. package/README.md +22 -0
  4. package/package.json +9 -2
  5. package/skills/app-dev/airflow/SKILL.md +1 -1
  6. package/skills/app-dev/airflow/release-check.mjs +3 -1
  7. package/skills/app-dev/android-foundation/SKILL.md +1 -1
  8. package/skills/app-dev/android-foundation/release-check.mjs +3 -1
  9. package/skills/app-dev/app-debug/SKILL.md +1 -1
  10. package/skills/app-dev/app-debug/release-check.mjs +3 -1
  11. package/skills/app-dev/better-auth/SKILL.md +1 -1
  12. package/skills/app-dev/better-auth/release-check.mjs +3 -1
  13. package/skills/app-dev/data-catalog/SKILL.md +1 -1
  14. package/skills/app-dev/data-catalog/release-check.mjs +3 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/data-quality/release-check.mjs +3 -1
  17. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  18. package/skills/app-dev/ios-foundation/release-check.mjs +3 -1
  19. package/skills/app-dev/monorepo/SKILL.md +1 -1
  20. package/skills/app-dev/monorepo/release-check.mjs +3 -1
  21. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  22. package/skills/app-dev/nextjs-fullstack/release-check.mjs +3 -1
  23. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  24. package/skills/app-dev/prisma-setup/release-check.mjs +3 -1
  25. package/skills/app-dev/pyspark/SKILL.md +1 -1
  26. package/skills/app-dev/pyspark/release-check.mjs +3 -1
  27. package/skills/app-dev/python-backend/SKILL.md +1 -1
  28. package/skills/app-dev/python-backend/release-check.mjs +3 -1
  29. package/skills/app-dev/shadcn/SKILL.md +1 -1
  30. package/skills/app-dev/shadcn/release-check.mjs +3 -1
  31. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  32. package/skills/app-dev/zod-v4/release-check.mjs +3 -1
  33. package/skills/cicd/cloud-build/SKILL.md +1 -1
  34. package/skills/cicd/cloud-build/release-check.mjs +3 -1
  35. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  36. package/skills/cicd/cloud-debug/release-check.mjs +3 -1
  37. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  38. package/skills/cicd/cloud-deploy/release-check.mjs +3 -1
  39. package/skills/cicd/docker/SKILL.md +1 -1
  40. package/skills/cicd/docker/release-check.mjs +3 -1
  41. package/skills/cicd/github-actions/SKILL.md +1 -1
  42. package/skills/cicd/github-actions/release-check.mjs +3 -1
  43. package/skills/cicd/github-flow/SKILL.md +1 -1
  44. package/skills/cicd/github-flow/release-check.mjs +3 -1
  45. package/skills/cicd/google-cloud/SKILL.md +1 -1
  46. package/skills/cicd/google-cloud/release-check.mjs +3 -1
  47. package/skills/cicd/mergify/SKILL.md +1 -1
  48. package/skills/cicd/mergify/release-check.mjs +3 -1
  49. package/skills/cicd/podman/SKILL.md +1 -1
  50. package/skills/cicd/podman/release-check.mjs +3 -1
  51. package/skills/content/article-review/SKILL.md +1 -1
  52. package/skills/content/article-review/release-check.mjs +3 -1
  53. package/skills/content/article-to-image/SKILL.md +1 -1
  54. package/skills/content/article-to-image/release-check.mjs +3 -1
  55. package/skills/content/flowchart-svg/SKILL.md +1 -1
  56. package/skills/content/flowchart-svg/release-check.mjs +3 -1
  57. package/skills/content/image-gen/SKILL.md +1 -1
  58. package/skills/content/image-gen/release-check.mjs +3 -1
  59. package/skills/content/mermaid/SKILL.md +1 -1
  60. package/skills/content/mermaid/release-check.mjs +3 -1
  61. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  62. package/skills/content/wechat-article-writer/release-check.mjs +3 -1
  63. package/skills/content/wechat-publisher/SKILL.md +1 -1
  64. package/skills/content/wechat-publisher/release-check.mjs +3 -1
  65. package/skills/content/wechat-search/SKILL.md +1 -1
  66. package/skills/content/wechat-search/release-check.mjs +3 -1
  67. package/skills/content/xai-search/SKILL.md +88 -13
  68. package/skills/content/xai-search/release-check.mjs +3 -1
  69. package/skills/content/xai-search/scripts/xai_search.sh +125 -149
  70. package/skills/core/distill-writing/SKILL.md +1 -1
  71. package/skills/core/distill-writing/release-check.mjs +3 -1
  72. package/skills/core/doc-to-md/SKILL.md +1 -1
  73. package/skills/core/doc-to-md/release-check.mjs +3 -1
  74. package/skills/core/editorial-review/SKILL.md +1 -1
  75. package/skills/core/editorial-review/release-check.mjs +3 -1
  76. package/skills/core/house-style/SKILL.md +1 -1
  77. package/skills/core/house-style/release-check.mjs +3 -1
  78. package/skills/core/plain-language/SKILL.md +1 -1
  79. package/skills/core/plain-language/release-check.mjs +3 -1
  80. package/skills/core/polish-cn/SKILL.md +1 -1
  81. package/skills/core/polish-cn/release-check.mjs +3 -1
  82. package/skills/core/reg-parser/SKILL.md +1 -1
  83. package/skills/core/reg-parser/release-check.mjs +3 -1
  84. package/skills/core/terminology/SKILL.md +1 -1
  85. package/skills/core/terminology/release-check.mjs +3 -1
  86. package/skills/core/write-doc/SKILL.md +1 -1
  87. package/skills/core/write-doc/release-check.mjs +3 -1
  88. package/skills/engineering/better-review/SKILL.md +1 -1
  89. package/skills/engineering/better-review/release-check.mjs +3 -1
  90. package/skills/engineering/contributing/SKILL.md +1 -1
  91. package/skills/engineering/contributing/release-check.mjs +3 -1
  92. package/skills/engineering/e2e-test/SKILL.md +1 -1
  93. package/skills/engineering/e2e-test/release-check.mjs +3 -1
  94. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  95. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  96. package/skills/engineering/engineering-runtime/release-check.mjs +3 -1
  97. package/skills/engineering/engineering-runtime/scripts/installation-resolver.mjs +191 -0
  98. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +51 -10
  99. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  100. package/skills/engineering/ensure-coverage/release-check.mjs +3 -1
  101. package/skills/engineering/golden-path/SKILL.md +1 -1
  102. package/skills/engineering/golden-path/release-check.mjs +3 -1
  103. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  104. package/skills/engineering/harness-runtime/release-check.mjs +3 -1
  105. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  106. package/skills/engineering/resolve-issues/references/preflight.md +7 -0
  107. package/skills/engineering/resolve-issues/references/shared-modules.md +27 -0
  108. package/skills/engineering/resolve-issues/references/task-plan.md +5 -1
  109. package/skills/engineering/resolve-issues/release-check.mjs +3 -1
  110. package/skills/engineering/resolve-issues/scripts/active-installation.mjs +32 -0
  111. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +3 -0
  112. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +17 -1
  113. package/skills/engineering/resolve-release/SKILL.md +1 -1
  114. package/skills/engineering/resolve-release/release-check.mjs +3 -1
  115. package/skills/engineering/rush-issues/SKILL.md +1 -1
  116. package/skills/engineering/rush-issues/release-check.mjs +3 -1
  117. package/skills/engineering/rush-release/SKILL.md +1 -1
  118. package/skills/engineering/rush-release/release-check.mjs +3 -1
  119. package/skills/engineering/test-quality/SKILL.md +1 -1
  120. package/skills/engineering/test-quality/release-check.mjs +3 -1
  121. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  122. package/skills/risk-compliance/compliance-audit/release-check.mjs +3 -1
  123. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  124. package/skills/risk-compliance/contract-compare/release-check.mjs +3 -1
  125. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  126. package/skills/risk-compliance/contract-review/release-check.mjs +3 -1
  127. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  128. package/skills/risk-compliance/doc-authenticity/release-check.mjs +3 -1
  129. package/skills/risk-compliance/fraud-analysis/SKILL.md +1 -1
  130. package/skills/risk-compliance/fraud-analysis/release-check.mjs +3 -1
  131. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  132. package/skills/risk-compliance/payment-analysis/release-check.mjs +3 -1
  133. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  134. package/skills/risk-compliance/similar-domain/release-check.mjs +3 -1
  135. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
  136. package/skills/risk-compliance/tld-detection/release-check.mjs +3 -1
  137. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +0 -436
  138. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +0 -36
  139. package/skills/app-dev/data-quality/scripts/data-quality.test.mjs +0 -12
  140. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +0 -637
  141. package/skills/cicd/github-flow/scripts/flow.test.mjs +0 -114
  142. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +0 -108
  143. package/skills/cicd/mergify/scripts/audit.test.mjs +0 -201
  144. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +0 -106
  145. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +0 -30
  146. package/skills/cicd/mergify/scripts/stack-push.test.mjs +0 -233
  147. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +0 -1826
  148. package/skills/content/image-gen/scripts/credentials.test.mjs +0 -212
  149. package/skills/content/image-gen/scripts/options.test.mjs +0 -245
  150. package/skills/content/xai-search/tests/smoke_test.sh +0 -36
  151. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +0 -244
  152. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +0 -165
  153. package/skills/engineering/better-review/scripts/pr-context.test.mjs +0 -168
  154. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +0 -587
  155. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +0 -414
  156. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +0 -96
  157. package/skills/engineering/contributing/scripts/persist-conventions.test.mjs +0 -118
  158. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +0 -106
  159. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +0 -117
  160. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +0 -69
  161. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +0 -98
  162. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +0 -71
  163. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +0 -59
  164. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +0 -76
  165. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +0 -68
  166. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +0 -95
  167. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +0 -288
  168. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +0 -96
  169. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +0 -96
  170. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +0 -47
  171. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +0 -86
  172. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +0 -447
  173. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +0 -552
  174. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +0 -289
  175. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +0 -119
  176. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +0 -1457
  177. package/skills/engineering/harness-runtime/discover-models.test.mjs +0 -363
  178. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +0 -967
  179. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +0 -829
  180. package/skills/engineering/harness-runtime/model-catalog.test.mjs +0 -366
  181. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +0 -280
  182. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +0 -346
  183. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +0 -88
  184. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +0 -264
  185. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +0 -79
  186. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +0 -107
  187. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +0 -14
  188. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +0 -149
  189. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +0 -915
  190. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +0 -156
  191. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +0 -448
  192. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +0 -217
  193. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +0 -91
  194. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +0 -1060
  195. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +0 -458
  196. package/skills/engineering/resolve-release/scripts/apply.test.mjs +0 -99
  197. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +0 -100
  198. package/skills/engineering/resolve-release/scripts/flow.test.mjs +0 -340
  199. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +0 -101
  200. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +0 -426
  201. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +0 -659
  202. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +0 -317
  203. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +0 -291
  204. package/skills/engineering/resolve-release/scripts/plan.test.mjs +0 -428
  205. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +0 -591
  206. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +0 -119
  207. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +0 -104
  208. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +0 -14
  209. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +0 -12
  210. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +0 -212
  211. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +0 -14
  212. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +0 -14
  213. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +0 -134
  214. 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
- });