orizu 0.5.20 → 0.5.21

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 (260) hide show
  1. package/dist/archive-artifacts-cli.d.ts +50 -0
  2. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  3. package/dist/artifact-archive.d.ts +48 -0
  4. package/dist/artifact-archive.d.ts.map +1 -0
  5. package/dist/artifact-pull.d.ts +22 -0
  6. package/dist/artifact-pull.d.ts.map +1 -0
  7. package/dist/banner.d.ts +3 -0
  8. package/dist/banner.d.ts.map +1 -0
  9. package/dist/connectors-cli.d.ts +31 -0
  10. package/dist/connectors-cli.d.ts.map +1 -0
  11. package/dist/credentials.d.ts +46 -0
  12. package/dist/credentials.d.ts.map +1 -0
  13. package/dist/csv.d.ts +49 -0
  14. package/dist/csv.d.ts.map +1 -0
  15. package/dist/dataset-download.d.ts +6 -0
  16. package/dist/dataset-download.d.ts.map +1 -0
  17. package/dist/dataset-edit-rows.d.ts +10 -0
  18. package/dist/dataset-edit-rows.d.ts.map +1 -0
  19. package/dist/dataset-upload-snapshot.d.ts +13 -0
  20. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  21. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  22. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  23. package/dist/daytona-slice.d.ts +106 -0
  24. package/dist/daytona-slice.d.ts.map +1 -0
  25. package/dist/deploy-key-merge-core.d.ts +121 -0
  26. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  27. package/dist/diff-comments-cli.d.ts +12 -0
  28. package/dist/diff-comments-cli.d.ts.map +1 -0
  29. package/dist/diff-comments-cli.js +3 -0
  30. package/dist/egress-allowlist-cli.d.ts +23 -0
  31. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  32. package/dist/egress-policy.d.ts +108 -0
  33. package/dist/egress-policy.d.ts.map +1 -0
  34. package/dist/error-response.d.ts +9 -0
  35. package/dist/error-response.d.ts.map +1 -0
  36. package/dist/file-parser.d.ts +5 -0
  37. package/dist/file-parser.d.ts.map +1 -0
  38. package/dist/gepa-engine-dispatch.d.ts +9 -0
  39. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  40. package/dist/gepa-engine-dispatch.js +48 -2
  41. package/dist/gepa-python-command.d.ts +6 -0
  42. package/dist/gepa-python-command.d.ts.map +1 -0
  43. package/dist/gepa-python-paths.d.ts +6 -0
  44. package/dist/gepa-python-paths.d.ts.map +1 -0
  45. package/dist/gepa-python-paths.js +6 -3
  46. package/dist/git-credential.d.ts +69 -0
  47. package/dist/git-credential.d.ts.map +1 -0
  48. package/dist/git-recovery-environment.d.ts +31 -0
  49. package/dist/git-recovery-environment.d.ts.map +1 -0
  50. package/dist/github-setup.d.ts +122 -0
  51. package/dist/github-setup.d.ts.map +1 -0
  52. package/dist/global-flags.d.ts +12 -0
  53. package/dist/global-flags.d.ts.map +1 -0
  54. package/dist/help.d.ts +46 -0
  55. package/dist/help.d.ts.map +1 -0
  56. package/dist/help.js +91 -23
  57. package/dist/hosted-boot.d.ts +273 -0
  58. package/dist/hosted-boot.d.ts.map +1 -0
  59. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  60. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap.d.ts +188 -0
  62. package/dist/hosted-bootstrap.d.ts.map +1 -0
  63. package/dist/hosted-broker-server.d.ts +50 -0
  64. package/dist/hosted-broker-server.d.ts.map +1 -0
  65. package/dist/hosted-git-http-server.d.ts +36 -0
  66. package/dist/hosted-git-http-server.d.ts.map +1 -0
  67. package/dist/hosted-harness-claude.d.ts +195 -0
  68. package/dist/hosted-harness-claude.d.ts.map +1 -0
  69. package/dist/hosted-harness-opencode.d.ts +227 -0
  70. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  71. package/dist/hosted-harness.d.ts +118 -0
  72. package/dist/hosted-harness.d.ts.map +1 -0
  73. package/dist/hosted-harvest.d.ts +68 -0
  74. package/dist/hosted-harvest.d.ts.map +1 -0
  75. package/dist/hosted-headless.d.ts +72 -0
  76. package/dist/hosted-headless.d.ts.map +1 -0
  77. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  78. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  79. package/dist/hosted-loop.d.ts +170 -0
  80. package/dist/hosted-loop.d.ts.map +1 -0
  81. package/dist/hosted-run-event-sink.d.ts +222 -0
  82. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  83. package/dist/hosted-runtime-assets.d.ts +186 -0
  84. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  85. package/dist/hosted-session-cli.d.ts +258 -0
  86. package/dist/hosted-session-cli.d.ts.map +1 -0
  87. package/dist/hosted-skill-staging.d.ts +73 -0
  88. package/dist/hosted-skill-staging.d.ts.map +1 -0
  89. package/dist/http.d.ts +8 -0
  90. package/dist/http.d.ts.map +1 -0
  91. package/dist/index.d.ts +22 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +88 -92
  94. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  95. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  96. package/dist/instruction-set-gepa-launch.js +15 -0
  97. package/dist/instruction-set-loader.d.ts +6 -0
  98. package/dist/instruction-set-loader.d.ts.map +1 -0
  99. package/dist/instruction-set-loader.js +59 -0
  100. package/dist/instruction-set-manifest.d.ts +14 -0
  101. package/dist/instruction-set-manifest.d.ts.map +1 -0
  102. package/dist/instruction-set-manifest.js +162 -0
  103. package/dist/instruction-sets-cli.d.ts +40 -0
  104. package/dist/instruction-sets-cli.d.ts.map +1 -0
  105. package/dist/instruction-sets-cli.js +361 -0
  106. package/dist/json-response.d.ts +4 -0
  107. package/dist/json-response.d.ts.map +1 -0
  108. package/dist/jsonl-stream.d.ts +8 -0
  109. package/dist/jsonl-stream.d.ts.map +1 -0
  110. package/dist/kill-switch-cli.d.ts +30 -0
  111. package/dist/kill-switch-cli.d.ts.map +1 -0
  112. package/dist/loopback-serve.d.ts +25 -0
  113. package/dist/loopback-serve.d.ts.map +1 -0
  114. package/dist/manifests-cli.d.ts +22 -0
  115. package/dist/manifests-cli.d.ts.map +1 -0
  116. package/dist/markdown-report-input.d.ts +7 -0
  117. package/dist/markdown-report-input.d.ts.map +1 -0
  118. package/dist/merge-job.d.ts +155 -0
  119. package/dist/merge-job.d.ts.map +1 -0
  120. package/dist/model-configs-cli.d.ts +19 -0
  121. package/dist/model-configs-cli.d.ts.map +1 -0
  122. package/dist/model-configs-cli.js +104 -0
  123. package/dist/optimization-export-cli.d.ts +19 -0
  124. package/dist/optimization-export-cli.d.ts.map +1 -0
  125. package/dist/optimizations-list-cli.d.ts +20 -0
  126. package/dist/optimizations-list-cli.d.ts.map +1 -0
  127. package/dist/optimizations-promote-cli.d.ts +8 -0
  128. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  129. package/dist/optimizations-promote-cli.js +34 -0
  130. package/dist/option-validation.d.ts +15 -0
  131. package/dist/option-validation.d.ts.map +1 -0
  132. package/dist/preview-runtime.d.ts +63 -0
  133. package/dist/preview-runtime.d.ts.map +1 -0
  134. package/dist/prompt-draft-push.d.ts +60 -0
  135. package/dist/prompt-draft-push.d.ts.map +1 -0
  136. package/dist/prompt-label-cli.d.ts +11 -0
  137. package/dist/prompt-label-cli.d.ts.map +1 -0
  138. package/dist/prompt-label-cli.js +29 -0
  139. package/dist/prompt-length-wire.d.ts +28 -0
  140. package/dist/prompt-length-wire.d.ts.map +1 -0
  141. package/dist/prompt-report-cli.d.ts +10 -0
  142. package/dist/prompt-report-cli.d.ts.map +1 -0
  143. package/dist/prompt-summary-table.d.ts +15 -0
  144. package/dist/prompt-summary-table.d.ts.map +1 -0
  145. package/dist/prompt-summary-table.js +6 -0
  146. package/dist/registration-tag-warning.d.ts +32 -0
  147. package/dist/registration-tag-warning.d.ts.map +1 -0
  148. package/dist/report-comments-cli.d.ts +16 -0
  149. package/dist/report-comments-cli.d.ts.map +1 -0
  150. package/dist/report-markdown-blankness.d.ts +2 -0
  151. package/dist/report-markdown-blankness.d.ts.map +1 -0
  152. package/dist/report-replacement-warning.d.ts +17 -0
  153. package/dist/report-replacement-warning.d.ts.map +1 -0
  154. package/dist/repository-backup-core.d.ts +225 -0
  155. package/dist/repository-backup-core.d.ts.map +1 -0
  156. package/dist/repository-recovery-manifest.d.ts +83 -0
  157. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  158. package/dist/run-event-digest.d.ts +74 -0
  159. package/dist/run-event-digest.d.ts.map +1 -0
  160. package/dist/runner-dir-verify.d.ts +66 -0
  161. package/dist/runner-dir-verify.d.ts.map +1 -0
  162. package/dist/runner-instruction-set-contract.d.ts +24 -0
  163. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  164. package/dist/runner-instruction-set-contract.js +50 -0
  165. package/dist/sandbox-hygiene.d.ts +45 -0
  166. package/dist/sandbox-hygiene.d.ts.map +1 -0
  167. package/dist/sandbox-provider.d.ts +193 -0
  168. package/dist/sandbox-provider.d.ts.map +1 -0
  169. package/dist/scorer-draft-push.d.ts +50 -0
  170. package/dist/scorer-draft-push.d.ts.map +1 -0
  171. package/dist/scorers-cli.d.ts +20 -0
  172. package/dist/scorers-cli.d.ts.map +1 -0
  173. package/dist/scorers-cli.js +22 -0
  174. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  175. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  176. package/dist/scorers-verify-parity-bridge.js +90 -0
  177. package/dist/scorers-verify-parity.d.ts +81 -0
  178. package/dist/scorers-verify-parity.d.ts.map +1 -0
  179. package/dist/scorers-verify-parity.js +502 -0
  180. package/dist/scores-accept-cli.d.ts +21 -0
  181. package/dist/scores-accept-cli.d.ts.map +1 -0
  182. package/dist/secret-redaction.d.ts +47 -0
  183. package/dist/secret-redaction.d.ts.map +1 -0
  184. package/dist/session-timeline.d.ts +54 -0
  185. package/dist/session-timeline.d.ts.map +1 -0
  186. package/dist/setup-prompt.d.ts +5 -0
  187. package/dist/setup-prompt.d.ts.map +1 -0
  188. package/dist/skill-installer.d.ts +68 -0
  189. package/dist/skill-installer.d.ts.map +1 -0
  190. package/dist/skilled-proposer-launch.d.ts +7 -0
  191. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  192. package/dist/skilled-proposer-launch.js +60 -0
  193. package/dist/smoke.d.ts +102 -0
  194. package/dist/smoke.d.ts.map +1 -0
  195. package/dist/start-intent.d.ts +12 -0
  196. package/dist/start-intent.d.ts.map +1 -0
  197. package/dist/task-assignment-manifest.d.ts +15 -0
  198. package/dist/task-assignment-manifest.d.ts.map +1 -0
  199. package/dist/task-create-error.d.ts +18 -0
  200. package/dist/task-create-error.d.ts.map +1 -0
  201. package/dist/team-connectors-cli.d.ts +29 -0
  202. package/dist/team-connectors-cli.d.ts.map +1 -0
  203. package/dist/types.d.ts +39 -0
  204. package/dist/types.d.ts.map +1 -0
  205. package/dist/vercel-sandbox-provider.d.ts +156 -0
  206. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  207. package/dist/workbench-cli.d.ts +233 -0
  208. package/dist/workbench-cli.d.ts.map +1 -0
  209. package/dist/workspace-sync.d.ts +207 -0
  210. package/dist/workspace-sync.d.ts.map +1 -0
  211. package/dist/workspace.d.ts +92 -0
  212. package/dist/workspace.d.ts.map +1 -0
  213. package/dist/zip-draft-push.d.ts +55 -0
  214. package/dist/zip-draft-push.d.ts.map +1 -0
  215. package/package.json +14 -1
  216. package/requirements/README.md +138 -0
  217. package/requirements/skilled-proposer.in +66 -0
  218. package/requirements/skilled-proposer.lock +265 -0
  219. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  220. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  221. package/src/diff-comments-cli.ts +4 -0
  222. package/src/gepa-engine-dispatch.ts +42 -2
  223. package/src/gepa-python-paths.ts +6 -3
  224. package/src/help.ts +93 -23
  225. package/src/index.ts +99 -126
  226. package/src/instruction-set-gepa-launch.ts +28 -0
  227. package/src/instruction-set-loader.ts +37 -0
  228. package/src/instruction-set-manifest.ts +120 -0
  229. package/src/instruction-sets-cli.ts +306 -0
  230. package/src/model-configs-cli.ts +119 -0
  231. package/src/optimizations-promote-cli.ts +41 -0
  232. package/src/prompt-label-cli.ts +55 -0
  233. package/src/prompt-summary-table.ts +12 -0
  234. package/src/runner-instruction-set-contract.ts +64 -0
  235. package/src/scorers-cli.ts +40 -0
  236. package/src/scorers-verify-parity-bridge.ts +90 -0
  237. package/src/scorers-verify-parity.ts +564 -0
  238. package/src/skilled-proposer-launch.ts +89 -0
  239. package/src/skilled-proposer-venv-manager.mjs +757 -0
  240. package/tsconfig.json +2 -0
  241. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +2 -1
  242. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +8 -2
  243. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +17 -3
  244. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +100 -8
  245. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  246. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +92 -12
  247. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +100 -22
  248. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  249. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +17 -0
  250. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +2 -1
  251. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  252. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +4 -1
  253. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  254. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  255. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  256. package/vendor/skills/orizu-cli/SKILL.md +2 -0
  257. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  258. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  259. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +25 -0
  260. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +108 -8
package/src/index.ts CHANGED
@@ -66,11 +66,15 @@ import {
66
66
  workspaceExists,
67
67
  } from './workspace.js'
68
68
  import { connectorsCommand } from './connectors-cli.js'
69
+ import { modelConfigsCommand } from './model-configs-cli.js'
69
70
  import { ARTIFACT_MAX_BYTES, runnerOptimizerCommand } from './artifact-pull.js'
70
71
  import { killSwitchCommand } from './kill-switch-cli.js'
71
72
  import { egressAllowlistCommand } from './egress-allowlist-cli.js'
72
73
  import { acceptScoreRunCommand } from './scores-accept-cli.js'
73
74
  import { listOptimizationRunsCommand } from './optimizations-list-cli.js'
75
+ import { promoteOptimizationCommand } from './optimizations-promote-cli.js'
76
+ import { scorersCommand } from './scorers-cli.js'
77
+ import { verifyScorerParityCommand } from './scorers-verify-parity.js'
74
78
  import {
75
79
  archiveArtifactCommand,
76
80
  listAssignmentsCommand,
@@ -85,9 +89,12 @@ import { workbenchCommand } from './workbench-cli.js'
85
89
  import { hostedCommand } from './hosted-session-cli.js'
86
90
  import { workspaceSyncCommand } from './workspace-sync.js'
87
91
  import { runGitCredentialInvocation } from './git-credential.js'
92
+ import { instructionSetsCommand, syncToDisk } from './instruction-sets-cli.js'; export { loadInstructionSet } from './instruction-set-loader.js'
93
+ import { runnerInputInstructionSet, runnerInputPrompt, runnerInstructionSetSyncSet, type RunnerInstructionSet } from './runner-instruction-set-contract.js'
88
94
  import { pushPromptDraft } from './prompt-draft-push.js'
89
95
  import { readMarkdownReportInput } from './markdown-report-input.js'
90
96
  import { promptPushErrorMessage, promptReportCommand } from './prompt-report-cli.js'
97
+ import { setPromptLabel } from './prompt-label-cli.js'
91
98
  import { printPromptSummaryTable } from './prompt-summary-table.js'
92
99
  import type {
93
100
  CliLengthMeasurementUnavailableReason,
@@ -107,8 +114,10 @@ import {
107
114
  import { diffCommentsCommand } from './diff-comments-cli.js'
108
115
  import { exportOptimizationRunCommand } from './optimization-export-cli.js'
109
116
  import { dispatchGepaEngine } from './gepa-engine-dispatch.js'
117
+ import { resolveGepaInstructionSetProfileVersion } from './instruction-set-gepa-launch.js'
110
118
  import { getGepaPythonCommand } from './gepa-python-command.js'
111
119
  import { getGepaPythonPathEntries } from './gepa-python-paths.js'
120
+ import { prepareSkilledProposerLaunch } from './skilled-proposer-launch.js'
112
121
 
113
122
  export { parseJsonResponse, sanitizeTerminalText } from './json-response.js'
114
123
 
@@ -195,6 +204,10 @@ interface PromptSummary {
195
204
  lengthStatsUnavailableReason?: CliLengthMeasurementUnavailableReason
196
205
  lengthStatsVersionId?: string
197
206
  lengthStatsVersionNumber?: number
207
+ owner?: {
208
+ instructionSetSlug: string
209
+ componentKey: string
210
+ } | null
198
211
  }
199
212
 
200
213
  interface TaskReportPayload {
@@ -229,7 +242,7 @@ interface ScoreSubmitInput {
229
242
 
230
243
  interface RunnerExecContext {
231
244
  prompt: {
232
- body: string | null
245
+ body?: string | null
233
246
  bodyKind: string
234
247
  providerSettings: Record<string, unknown>
235
248
  promptId?: string
@@ -241,6 +254,7 @@ interface RunnerExecContext {
241
254
  metricKey: string
242
255
  higherIsBetter: boolean
243
256
  } | null
257
+ instructionSet?: RunnerInstructionSet
244
258
  rows: Array<{
245
259
  id: string
246
260
  row: Record<string, unknown>
@@ -1634,6 +1648,10 @@ async function pullPromptArtifact(kind: 'prompt' | 'judge') {
1634
1648
  metricKey: string
1635
1649
  higherIsBetter: boolean
1636
1650
  }>
1651
+ owner?: {
1652
+ instructionSetSlug: string
1653
+ componentKey: string
1654
+ } | null
1637
1655
  }>(response, `${kind} pull`)
1638
1656
 
1639
1657
  const targetDir = expandHomePath(outDir)
@@ -1720,6 +1738,7 @@ async function pullPromptArtifact(kind: 'prompt' | 'judge') {
1720
1738
  scorer_version_id: data.scorerVersionId || undefined,
1721
1739
  scorer_version_ids: scorerVersions.map(item => item.id),
1722
1740
  path: targetDir,
1741
+ owner: data.owner || undefined,
1723
1742
  })
1724
1743
  return
1725
1744
  }
@@ -1735,73 +1754,17 @@ async function pullPromptArtifact(kind: 'prompt' | 'judge') {
1735
1754
  `Warning: prompt version ${sanitizeTerminalText(data.version.id)} does not map to an executable scorer version.`
1736
1755
  )
1737
1756
  }
1738
- }
1739
-
1740
- async function setPromptLabel() {
1741
- const promptName = getPositionalArg(3)
1742
- const label = getPositionalArg(4)
1743
- const project = getArg('--project') || await resolveProjectSlug(null)
1744
- const promptVersionId = getArg('--version')
1745
-
1746
- if (!promptName || !label || !promptVersionId) {
1747
- throw new Error('Usage: orizu prompts labels set <prompt-name> <label> --version <prompt-version-id> [--project <team/project>] [--json]')
1748
- }
1749
-
1750
- const response = await authedFetch(`/api/cli/prompts/labels?project=${encodeURIComponent(project)}`, {
1751
- method: 'POST',
1752
- headers: { 'Content-Type': 'application/json' },
1753
- body: JSON.stringify({
1754
- promptName,
1755
- label,
1756
- promptVersionId,
1757
- }),
1758
- })
1759
-
1760
- if (!response.ok) {
1761
- throw new Error(`Failed to set prompt label: ${await response.text()}`)
1762
- }
1763
-
1764
- const data = await parseJsonResponse<Record<string, unknown>>(response, 'Prompt label set')
1765
- if (hasJsonFlag()) {
1766
- printJson(data)
1767
- return
1757
+ if (kind === 'prompt' && data.owner) {
1758
+ printLine(
1759
+ `Owner: ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)}) -> ` +
1760
+ `${sanitizeTerminalText(data.owner.instructionSetSlug)} / ${sanitizeTerminalText(data.owner.componentKey)}`
1761
+ )
1768
1762
  }
1769
-
1770
- printLine(`Moved ${sanitizeTerminalText(label)} to ${sanitizeTerminalText(promptVersionId)}`)
1771
1763
  }
1772
1764
 
1773
- async function setPromptArchivedState(archived: boolean) {
1774
- const promptRef = getPositionalArg(2)
1775
- const project = getArg('--project') || await resolveProjectSlug(null)
1776
- const command = archived ? 'archive' : 'restore'
1777
-
1778
- if (!promptRef) {
1779
- throw new Error(`Usage: orizu prompts ${command} <prompt-id-or-name> --project <team/project> [--json]`)
1780
- }
1781
-
1782
- const response = await authedFetch(`/api/cli/prompts/${encodeURIComponent(promptRef)}?project=${encodeURIComponent(project)}`, {
1783
- method: 'PATCH',
1784
- headers: { 'Content-Type': 'application/json' },
1785
- body: JSON.stringify({ archived }),
1786
- })
1787
-
1788
- if (!response.ok) {
1789
- throw new Error(`Failed to ${command} prompt: ${await response.text()}`)
1790
- }
1791
-
1792
- const data = await parseJsonResponse<{
1793
- prompt: {
1794
- id: string
1795
- name: string
1796
- status: string
1797
- }
1798
- }>(response, `Prompt ${command}`)
1799
- if (hasJsonFlag()) {
1800
- printJson(data)
1801
- return
1802
- }
1803
-
1804
- printLine(`${archived ? 'Archived' : 'Restored'} prompt ${sanitizeTerminalText(data.prompt.name)} (${sanitizeTerminalText(data.prompt.id)})`)
1765
+ function refusePromptMutation(replacement: string): never {
1766
+ process.exitCode = 1
1767
+ throw new Error(`Use: orizu instructions ${replacement}`)
1805
1768
  }
1806
1769
 
1807
1770
  async function bindPromptScorer(role: 'headline' | 'tracked') {
@@ -1830,13 +1793,25 @@ async function bindPromptScorer(role: 'headline' | 'tracked') {
1830
1793
  throw new Error(`Failed to bind prompt scorer: ${await response.text()}`)
1831
1794
  }
1832
1795
 
1833
- const data = await parseJsonResponse<Record<string, unknown>>(response, 'Prompt scorer bind')
1796
+ const data = await parseJsonResponse<{
1797
+ promptScorer: Record<string, unknown>
1798
+ owner?: {
1799
+ instructionSetSlug: string
1800
+ componentKey: string
1801
+ }
1802
+ }>(response, 'Prompt scorer bind')
1834
1803
  if (hasJsonFlag()) {
1835
1804
  printJson(data)
1836
1805
  return
1837
1806
  }
1838
1807
 
1839
1808
  printLine(`${role === 'headline' ? 'Set headline' : 'Added'} scorer ${sanitizeTerminalText(scorerVersionId)} for ${sanitizeTerminalText(promptId)}`)
1809
+ if (data.owner) {
1810
+ printLine(
1811
+ `Owner: ${sanitizeTerminalText(promptId)} -> ` +
1812
+ `${sanitizeTerminalText(data.owner.instructionSetSlug)} / ${sanitizeTerminalText(data.owner.componentKey)}`
1813
+ )
1814
+ }
1840
1815
  }
1841
1816
 
1842
1817
  function getPositionalArg(index: number): string | null {
@@ -2522,14 +2497,12 @@ async function updateOptimizationRunLifecycle(action: OptimizationLifecycleActio
2522
2497
 
2523
2498
  // ALI-1073 list moved to optimizations-list-cli.ts (line ratchet, ALI-976);
2524
2499
  // ALI-1175 labels its best scores as agent-reported evidence there.
2525
-
2526
2500
  async function runGepaOptimization() {
2527
2501
  const project = getArg('--project') || await resolveProjectSlug(null)
2528
2502
  const baseUrl = getBaseUrl()
2529
2503
  const runGepa = getGepaPythonCommand(cliArgs.slice(2), process.env)
2530
2504
  const python = runGepa.python
2531
2505
  let forwardedArgs = runGepa.args
2532
-
2533
2506
  // ALI-1159 (ADR-007): GEPA executes ad-hoc local runner dirs while
2534
2507
  // attributing records to registered runner version ids — verify the bytes
2535
2508
  // ARE those versions before the optimizer spawns. Scans the forwarded argv
@@ -2538,7 +2511,6 @@ async function runGepaOptimization() {
2538
2511
  // so a post-hash mutation of the original dirs cannot change what runs.
2539
2512
  const verified = await verifyGepaRunnerDirsFromArgs(forwardedArgs)
2540
2513
  forwardedArgs = verified.args
2541
-
2542
2514
  let result
2543
2515
  let selectedEngine = 'official'
2544
2516
  try {
@@ -2548,11 +2520,9 @@ async function runGepaOptimization() {
2548
2520
  // rotation crossing the hash + lookup round-trips cannot hand the child a
2549
2521
  // stale bearer.
2550
2522
  const token = resolveAuthTokenForBaseUrl(baseUrl)
2551
-
2552
2523
  if (!forwardedArgs.includes('--project')) {
2553
2524
  forwardedArgs = ['--project', project, ...forwardedArgs]
2554
2525
  }
2555
-
2556
2526
  const dispatch = dispatchGepaEngine(forwardedArgs, project, {
2557
2527
  ...process.env,
2558
2528
  ORIZU_API_URL: baseUrl,
@@ -2562,16 +2532,17 @@ async function runGepaOptimization() {
2562
2532
  PYTHONPATH: getGepaPythonPathEntries(process.env.PYTHONPATH).join(delimiter),
2563
2533
  PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || '1',
2564
2534
  })
2535
+ const instructionSetName = dispatch.environment.ORIZU_INSTRUCTION_SET_NAME
2536
+ const modelConfigIdentity = dispatch.environment.ORIZU_MODEL_CONFIG_IDENTITY
2537
+ const instructionSetProfileVersionId = instructionSetName && modelConfigIdentity
2538
+ ? await resolveGepaInstructionSetProfileVersion(instructionSetName, modelConfigIdentity, project) : undefined
2539
+ if (instructionSetProfileVersionId) dispatch.environment.ORIZU_INSTRUCTION_SET_PROFILE_VERSION_ID = instructionSetProfileVersionId
2565
2540
  selectedEngine = dispatch.engine
2566
-
2567
- result = spawnSync(python, ['-m', dispatch.module, ...dispatch.args], {
2568
- stdio: 'inherit',
2569
- env: dispatch.environment,
2570
- })
2541
+ const launch = prepareSkilledProposerLaunch(python, dispatch.engine, dispatch.environment)
2542
+ result = spawnSync(launch.python, ['-m', dispatch.module, ...dispatch.args], { stdio: 'inherit', env: launch.environment })
2571
2543
  } finally {
2572
2544
  verified.cleanup()
2573
2545
  }
2574
-
2575
2546
  if (result.error) {
2576
2547
  throw new Error(`${selectedEngine} GEPA engine failed: ${result.error.message}`, { cause: result.error })
2577
2548
  }
@@ -5777,7 +5748,7 @@ async function downloadAnnotations() {
5777
5748
  printLine(`Saved ${format.toUpperCase()} export to ${sanitizeTerminalText(filename)}`)
5778
5749
  }
5779
5750
 
5780
- function readRunnerManifest(runnerDir: string): { command: string[]; supports_body_kind?: string[] } {
5751
+ export function readRunnerManifest(runnerDir: string): { command: string[]; supports_body_kind?: string[] } {
5781
5752
  const manifestPath = join(runnerDir, 'manifest.json')
5782
5753
  const raw = readSourceFile(manifestPath)
5783
5754
  const manifest = JSON.parse(raw) as {
@@ -5805,9 +5776,9 @@ function readRunnerManifest(runnerDir: string): { command: string[]; supports_bo
5805
5776
  }
5806
5777
  }
5807
5778
 
5808
- const RUNNER_TIMEOUT_MS = 120_000
5809
- const RUNNER_OUTPUT_MAX_BYTES = 2 * 1024 * 1024
5810
- const RUNNER_ENV_ALLOWLIST = new Set([
5779
+ export const RUNNER_TIMEOUT_MS = 120_000
5780
+ export const RUNNER_OUTPUT_MAX_BYTES = 2 * 1024 * 1024
5781
+ export const RUNNER_ENV_ALLOWLIST = new Set([
5811
5782
  'PATH',
5812
5783
  'SystemRoot',
5813
5784
  'WINDIR',
@@ -5825,7 +5796,7 @@ const RUNNER_ENV_ALLOWLIST = new Set([
5825
5796
  'GOOGLE_API_KEY',
5826
5797
  ])
5827
5798
 
5828
- function runnerSubprocessEnv(inputPath: string, outputPath: string): NodeJS.ProcessEnv {
5799
+ export function runnerSubprocessEnv(inputPath: string, outputPath: string, instructionSetDir?: string): NodeJS.ProcessEnv {
5829
5800
  const env = {} as NodeJS.ProcessEnv
5830
5801
  for (const key of RUNNER_ENV_ALLOWLIST) {
5831
5802
  const value = process.env[key]
@@ -5835,6 +5806,8 @@ function runnerSubprocessEnv(inputPath: string, outputPath: string): NodeJS.Proc
5835
5806
  }
5836
5807
  env.ORIZU_RUNNER_INPUT_PATH = inputPath
5837
5808
  env.ORIZU_RUNNER_OUTPUT_PATH = outputPath
5809
+ delete env.ORIZU_INSTRUCTION_SET_DIR
5810
+ if (instructionSetDir) env.ORIZU_INSTRUCTION_SET_DIR = instructionSetDir
5838
5811
  return env
5839
5812
  }
5840
5813
 
@@ -5847,7 +5820,7 @@ function boundedRunnerOutput(value: string | Buffer | null | undefined): string
5847
5820
  return `${buffer.subarray(0, RUNNER_OUTPUT_MAX_BYTES).toString('utf8')}\n[truncated]`
5848
5821
  }
5849
5822
 
5850
- async function materializeRunnerVersion(runnerVersionId: string): Promise<{ runnerDir: string; cleanup: () => void }> {
5823
+ export async function materializeRunnerVersion(runnerVersionId: string): Promise<{ runnerDir: string; cleanup: () => void }> {
5851
5824
  const response = await authedFetch(`/api/cli/runner-versions/${encodeURIComponent(runnerVersionId)}/download`)
5852
5825
  if (!response.ok) {
5853
5826
  throw new Error(`Failed to download runner version: ${await response.text()}`)
@@ -5941,7 +5914,6 @@ async function runnersExec() {
5941
5914
  })()
5942
5915
  : await materializeRunnerVersion(resolvedRunnerVersionId)
5943
5916
  const runnerDir = materializedRunner.runnerDir
5944
-
5945
5917
  try {
5946
5918
  assertSnapshotManifestConfined(runnerDir, runnerDirArg ? '--runner-dir' : '--runner-version')
5947
5919
  // Inside the try (codex round-6 P3): a bad manifest must not leak the dir.
@@ -5951,22 +5923,19 @@ async function runnersExec() {
5951
5923
  `Runner does not support prompt body kind '${context.prompt.bodyKind}'. Supported kinds: ${manifest.supports_body_kind.join(', ')}`
5952
5924
  )
5953
5925
  }
5954
-
5955
5926
  const resultLines: string[] = []
5956
5927
  for (const row of context.rows) {
5957
5928
  const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-'))
5958
5929
  const inputPath = join(tempDir, 'input.json')
5959
5930
  const outputPath = join(tempDir, 'output.json')
5960
-
5931
+ const instructionSetDir = context.instructionSet ? join(tempDir, 'instruction-set') : undefined
5961
5932
  try {
5962
5933
  const modelOutput = row.row.model_output ?? row.row.modelOutput ?? row.row.output ?? null
5934
+ if (instructionSetDir && context.instructionSet) syncToDisk(instructionSetDir, runnerInstructionSetSyncSet(context.instructionSet))
5963
5935
  writeFileSync(inputPath, JSON.stringify({
5964
5936
  row: row.row,
5965
- prompt: {
5966
- body: context.prompt.body,
5967
- body_kind: context.prompt.bodyKind,
5968
- provider_settings: context.prompt.providerSettings,
5969
- },
5937
+ prompt: runnerInputPrompt(context.prompt),
5938
+ ...(context.instructionSet ? { instruction_set: runnerInputInstructionSet(context.instructionSet) } : {}),
5970
5939
  subject: scorerVersion
5971
5940
  ? {
5972
5941
  type: 'scorer_row',
@@ -5994,7 +5963,7 @@ async function runnersExec() {
5994
5963
 
5995
5964
  const result = spawnSync(manifest.command[0], manifest.command.slice(1), {
5996
5965
  cwd: runnerDir,
5997
- env: runnerSubprocessEnv(inputPath, outputPath),
5966
+ env: runnerSubprocessEnv(inputPath, outputPath, instructionSetDir),
5998
5967
  encoding: 'utf8',
5999
5968
  maxBuffer: RUNNER_OUTPUT_MAX_BYTES,
6000
5969
  timeout: RUNNER_TIMEOUT_MS,
@@ -6226,8 +6195,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
6226
6195
  }
6227
6196
 
6228
6197
  if (command === 'prompts' && subcommand === 'push') {
6229
- await pushPromptArtifact('prompt')
6230
- return
6198
+ if (getArg('--session')) {
6199
+ await pushPromptArtifact('prompt')
6200
+ return
6201
+ }
6202
+ refusePromptMutation('push <manifest> --project <team/project>')
6231
6203
  }
6232
6204
 
6233
6205
  if (
@@ -6241,17 +6213,25 @@ export async function main(rawArgs = process.argv.slice(2)) {
6241
6213
  }
6242
6214
 
6243
6215
  if (command === 'prompts' && subcommand === 'archive') {
6244
- await setPromptArchivedState(true)
6245
- return
6216
+ refusePromptMutation('archive <slug-or-exact-name> --project <team/project>')
6246
6217
  }
6247
6218
 
6248
6219
  if (command === 'prompts' && subcommand === 'restore') {
6249
- await setPromptArchivedState(false)
6250
- return
6220
+ refusePromptMutation('restore <slug-or-exact-name> --project <team/project>')
6251
6221
  }
6252
6222
 
6253
6223
  if (command === 'prompts' && subcommand === 'labels' && cliArgs[2] === 'set') {
6254
- await setPromptLabel()
6224
+ if (getPositionalArg(4) === 'production') {
6225
+ refusePromptMutation('profiles promote <set> --project <team/project> --model-config <identity> --version <n>')
6226
+ }
6227
+ await setPromptLabel({
6228
+ getArg,
6229
+ getPositionalArg,
6230
+ json: hasJsonFlag(),
6231
+ printJson,
6232
+ printLine,
6233
+ resolveProjectSlug,
6234
+ })
6255
6235
  return
6256
6236
  }
6257
6237
 
@@ -6302,29 +6282,17 @@ export async function main(rawArgs = process.argv.slice(2)) {
6302
6282
  return
6303
6283
  }
6304
6284
 
6305
- if (command === 'scorers' && subcommand === 'list') {
6306
- await listScorers()
6307
- return
6308
- }
6309
-
6310
- if (command === 'scorers' && subcommand === 'register') {
6311
- await registerScorer()
6312
- return
6313
- }
6314
-
6315
- if (command === 'scorers' && subcommand === 'detail') {
6316
- await showScorerDetail()
6317
- return
6318
- }
6319
-
6320
- if (command === 'scorers' && subcommand === 'labels' && cliArgs[2] === 'set') {
6321
- await setScorerLabel()
6322
- return
6323
- }
6324
-
6325
- if (command === 'scorers' && subcommand === 'exec') {
6326
- await execScorer()
6327
- return
6285
+ if (command === 'scorers') {
6286
+ const scorersExit = await scorersCommand(cliArgs, {
6287
+ list: listScorers, register: registerScorer, detail: showScorerDetail,
6288
+ labelsSet: setScorerLabel, exec: execScorer,
6289
+ verifyParity: args => verifyScorerParityCommand(args, {
6290
+ json: hasJsonFlag(), print: printLine, printErr: printError,
6291
+ materializeRunnerVersion, readRunnerManifest, runnerSubprocessEnv,
6292
+ timeoutMs: RUNNER_TIMEOUT_MS, maxOutputBytes: RUNNER_OUTPUT_MAX_BYTES,
6293
+ }),
6294
+ })
6295
+ if (scorersExit !== null) { process.exitCode = scorersExit; return }
6328
6296
  }
6329
6297
 
6330
6298
  if ((command === 'runners' || command === 'optimizers') && subcommand === 'push') {
@@ -6385,7 +6353,12 @@ export async function main(rawArgs = process.argv.slice(2)) {
6385
6353
  await listOptimizationRunsCommand(cliArgs.slice(2), { json: hasJsonFlag(), print: printLine, resolveProjectSlug })
6386
6354
  return
6387
6355
  }
6388
-
6356
+ if (command === 'optimizations' && subcommand === 'promote') {
6357
+ await promoteOptimizationCommand(cliArgs.slice(2), {
6358
+ json: hasJsonFlag(), print: printLine, resolveProjectSlug, fetcher: authedFetch,
6359
+ })
6360
+ return
6361
+ }
6389
6362
  if (
6390
6363
  command === 'optimizations' &&
6391
6364
  (
@@ -6549,11 +6522,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
6549
6522
  }
6550
6523
  if (command === 'git-credential') { process.exitCode = await runGitCredentialInvocation(cliArgs.slice(1), { stdin: readFileSync(0, 'utf8'), cwd: process.cwd(), print: printLine, printErr: printError }); return }
6551
6524
  if (command === 'github' && subcommand === 'link') { const noInput = hasArg('--no-input') || hasArg('--non-interactive') || !isInteractiveTerminal(); const team = getOptionalArgValue('--team') || (await resolveSetupTeam(null, noInput, false)).slug; await runGithubLink(team, { print: printLine, fetcher: authedFetch, openUrl: openInBrowser, confirm: (org) => askYesNo(`Connect this team to GitHub org '${org}'?`, true) }); return }
6552
- if (command === 'connectors' || command === 'manifests') {
6553
- const moduleCommand = command === 'connectors' ? connectorsCommand : manifestsCommand
6554
- process.exitCode = await moduleCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, resolveProjectSlug })
6525
+ if (command === 'connectors' || command === 'manifests' || command === 'model-configs') {
6526
+ process.exitCode = await (command === 'connectors' ? connectorsCommand : command === 'manifests' ? manifestsCommand : modelConfigsCommand)(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, resolveProjectSlug })
6555
6527
  return
6556
6528
  }
6529
+ if (command === 'instructions' || command === 'instruction-sets') { process.exitCode = await instructionSetsCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, resolveProjectSlug }); return }
6557
6530
  if (command === 'team' && (subcommand === 'kill-agents' || subcommand === 'release-agents')) {
6558
6531
  process.exitCode = await killSwitchCommand(cliArgs.slice(1), { json: hasJsonFlag(), print: printLine, printErr: printError })
6559
6532
  return
@@ -0,0 +1,28 @@
1
+ import { authedFetch } from './http.js'
2
+ import { parseJsonResponse } from './json-response.js'
3
+
4
+ interface InstructionSetResolution {
5
+ instructionSet?: {
6
+ default?: { profileVersionId?: string } | null
7
+ profiles?: Array<{
8
+ modelConfigIdentity?: string | null
9
+ production?: { profileVersionId?: string } | null
10
+ }>
11
+ }
12
+ }
13
+
14
+ /** Resolve the same production-or-default tuple served for a model identity. */
15
+ export async function resolveGepaInstructionSetProfileVersion(
16
+ instructionSetName: string,
17
+ modelConfigIdentity: string,
18
+ project: string,
19
+ ): Promise<string> {
20
+ const response = await authedFetch(`/api/cli/instruction-sets/${encodeURIComponent(instructionSetName)}?project=${encodeURIComponent(project)}`)
21
+ if (!response.ok) throw new Error(`Instruction set resolution failed: ${await response.text()}`)
22
+ const payload = await parseJsonResponse<InstructionSetResolution>(response, 'Instruction set resolution')
23
+ const instructionSet = payload.instructionSet
24
+ const profile = instructionSet?.profiles?.find(candidate => candidate.modelConfigIdentity === modelConfigIdentity)
25
+ const profileVersionId = profile?.production?.profileVersionId || instructionSet?.default?.profileVersionId
26
+ if (!profileVersionId) throw new Error('instruction_set_profile_version_unresolvable')
27
+ return profileVersionId
28
+ }
@@ -0,0 +1,37 @@
1
+ import { existsSync, realpathSync, readFileSync, statSync } from 'node:fs'
2
+ import { resolve, sep } from 'node:path'
3
+
4
+ export class InstructionSetLoaderError extends Error {
5
+ constructor(readonly code: string) { super(code); this.name = 'InstructionSetLoaderError' }
6
+ }
7
+
8
+ interface Material { files: Record<string, string>; pinnedComponents?: Record<string, unknown> }
9
+ interface Manifest { manifestVersion: number; name: string; shape: string[]; default: Material; profiles: Array<{ modelConfigIdentity: string; production: Material | null }>; filteredTo?: string[] }
10
+
11
+ function inside(root: string, candidate: string) { return candidate === root || candidate.startsWith(`${root}${sep}`) }
12
+
13
+ export function loadInstructionSet(directory: string, name: string, modelConfigIdentity: string): Record<string, string> {
14
+ const unresolvedRoot = resolve(directory, name)
15
+ if (!existsSync(unresolvedRoot) || !statSync(unresolvedRoot).isDirectory()) throw new InstructionSetLoaderError('instruction_set_not_synced')
16
+ const setRoot = realpathSync(unresolvedRoot)
17
+ const manifestPath = resolve(setRoot, 'manifest.json')
18
+ if (!existsSync(manifestPath)) throw new InstructionSetLoaderError('instruction_set_manifest_missing')
19
+ let manifest: Manifest
20
+ try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as Manifest } catch { throw new InstructionSetLoaderError('instruction_set_manifest_missing') }
21
+ if (manifest.manifestVersion !== 1) throw new InstructionSetLoaderError('instruction_set_manifest_version_unsupported')
22
+ if (manifest.filteredTo && !manifest.filteredTo.includes(modelConfigIdentity)) throw new InstructionSetLoaderError('instruction_set_profile_not_synced')
23
+ const selected = manifest.profiles.find(profile => profile.modelConfigIdentity === modelConfigIdentity)?.production || manifest.default
24
+ const result: Record<string, string> = {}
25
+ for (const key of manifest.shape) {
26
+ if (selected.pinnedComponents?.[key]) throw new InstructionSetLoaderError('instruction_set_component_unavailable')
27
+ const relativePath = selected.files?.[key]
28
+ if (!relativePath) throw new InstructionSetLoaderError('instruction_set_profile_key_missing')
29
+ const unresolvedPath = resolve(setRoot, relativePath)
30
+ if (!inside(setRoot, unresolvedPath)) throw new InstructionSetLoaderError('instruction_set_path_unsafe')
31
+ let path: string
32
+ try { path = realpathSync(unresolvedPath) } catch { throw new InstructionSetLoaderError('instruction_set_component_unreadable') }
33
+ if (!inside(setRoot, path)) throw new InstructionSetLoaderError('instruction_set_path_unsafe')
34
+ try { result[key] = readFileSync(path, 'utf8') } catch { throw new InstructionSetLoaderError('instruction_set_component_unreadable') }
35
+ }
36
+ return result
37
+ }
@@ -0,0 +1,120 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs'
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path'
3
+ import { createHash } from 'node:crypto'
4
+
5
+ export interface InstructionSetComponent { key: string; modelConfig?: string; text: string }
6
+ export interface InstructionSetManifest { name: string; description?: string | null; shape: string[]; components: InstructionSetComponent[] }
7
+
8
+ // Keep file-backed component reads within the same text budget used by prompt
9
+ // tooling; request assembly never asks Node to allocate an unbounded body.
10
+ export const MAX_INSTRUCTION_SET_COMPONENT_BYTES = 512 * 1024
11
+
12
+ const fail = (code: string): never => { throw new Error(code) }
13
+ const sha256 = (value: string) => createHash('sha256').update(value, 'utf8').digest('hex')
14
+
15
+ function hasPinnedComponents(material: unknown): boolean {
16
+ if (!material || typeof material !== 'object') return false
17
+ const pinned = (material as Record<string, unknown>).pinnedComponents
18
+ return Boolean(pinned && typeof pinned === 'object' && Object.keys(pinned).length > 0)
19
+ }
20
+
21
+ export function loadInstructionSetManifest(path: string): InstructionSetManifest {
22
+ let value: unknown
23
+ let source: string
24
+ try { source = readFileSync(path, 'utf8') } catch { return fail('instruction_set_path_not_found') }
25
+ try { value = JSON.parse(source) } catch { return fail('instruction_set_manifest_invalid_json') }
26
+ if (!value || typeof value !== 'object') return fail('instruction_set_manifest_invalid')
27
+ const input = value as Record<string, unknown>
28
+ if (typeof input.name !== 'string' || input.name.trim() === '') return fail('instruction_set_manifest_invalid_name')
29
+ const hasDescription = Object.hasOwn(input, 'description')
30
+ if (hasDescription && input.description !== null && typeof input.description !== 'string') return fail('instruction_set_manifest_invalid_description')
31
+ const description = hasDescription
32
+ ? input.description === '' ? null : input.description as string | null
33
+ : undefined
34
+ if (hasPinnedComponents(input.default)) return fail('instruction_set_component_unavailable')
35
+ if (!Array.isArray(input.shape) || input.shape.length === 0 || input.shape.some(key => typeof key !== 'string' || key === '') || new Set(input.shape).size !== input.shape.length) return fail('instruction_set_manifest_invalid_shape')
36
+ if (!Array.isArray(input.components)) return fail('instruction_set_manifest_invalid_components')
37
+ let root: string
38
+ try { root = realpathSync(dirname(path)) } catch { return fail('instruction_set_path_not_found') }
39
+ const seen = new Set<string>()
40
+ const loadedComponents: Array<InstructionSetComponent & { changedSinceSync: boolean; hasSyncBaseline: boolean }> = []
41
+ for (const raw of input.components) {
42
+ if (!raw || typeof raw !== 'object') return fail('instruction_set_manifest_invalid_component')
43
+ const component = raw as Record<string, unknown>
44
+ if (typeof component.key !== 'string' || component.key === '' || !input.shape.includes(component.key)) return fail(component.key && !input.shape.includes(component.key) ? 'instruction_set_key_outside_shape' : 'instruction_set_manifest_invalid_component')
45
+ const modelConfig = component.modelConfig === undefined ? undefined : component.modelConfig
46
+ if (modelConfig !== undefined && (typeof modelConfig !== 'string' || modelConfig === '')) return fail('instruction_set_manifest_invalid_component')
47
+ const identity = `${component.key}\u0000${modelConfig ?? ''}`; if (seen.has(identity)) return fail('instruction_set_duplicate_component'); seen.add(identity)
48
+ let text: string
49
+ if (typeof component.text === 'string' && component.path === undefined) text = component.text
50
+ else if (typeof component.path === 'string' && component.text === undefined) {
51
+ if (isAbsolute(component.path)) return fail('instruction_set_path_escape')
52
+ const candidate = resolve(root, component.path)
53
+ if (relative(root, candidate).startsWith('..')) return fail('instruction_set_path_escape')
54
+ if (!existsSync(candidate)) return fail('instruction_set_path_not_found')
55
+ let target: string; try { target = realpathSync(candidate) } catch { return fail('instruction_set_path_not_found') }
56
+ if (relative(root, target).startsWith('..')) return fail('instruction_set_path_escape')
57
+ try { text = readFileSync(target, 'utf8') } catch { return fail('instruction_set_path_not_found') }
58
+ if (Buffer.byteLength(text, 'utf8') > MAX_INSTRUCTION_SET_COMPONENT_BYTES) return fail('instruction_set_component_too_large')
59
+ } else return fail('instruction_set_manifest_invalid_component')
60
+ if (Buffer.byteLength(text, 'utf8') > MAX_INSTRUCTION_SET_COMPONENT_BYTES) return fail('instruction_set_component_too_large')
61
+ const hasSyncBaseline = typeof component.syncedContentSha256 === 'string'
62
+ if (component.syncedContentSha256 !== undefined && (!hasSyncBaseline || !/^[a-f0-9]{64}$/u.test(component.syncedContentSha256 as string))) return fail('instruction_set_manifest_invalid_component')
63
+ loadedComponents.push({
64
+ key: component.key,
65
+ ...(modelConfig === undefined ? {} : { modelConfig }),
66
+ text,
67
+ hasSyncBaseline,
68
+ changedSinceSync: hasSyncBaseline && sha256(text) !== component.syncedContentSha256,
69
+ })
70
+ }
71
+ for (const key of input.shape) if (!loadedComponents.some(component => component.key === key)) return fail('instruction_set_missing_shape_key')
72
+ const defaultIdentity = input.default && typeof input.default === 'object'
73
+ && typeof (input.default as Record<string, unknown>).modelConfigIdentity === 'string'
74
+ ? (input.default as Record<string, unknown>).modelConfigIdentity as string
75
+ : null
76
+ let components = loadedComponents
77
+ if (defaultIdentity) {
78
+ if (input.manifestVersion === 1 && (input.shape as string[]).some(key => {
79
+ const bare = loadedComponents.find(component => component.key === key && component.modelConfig === undefined)
80
+ const tagged = loadedComponents.find(component => component.key === key && component.modelConfig === defaultIdentity)
81
+ return bare && tagged && (!bare.hasSyncBaseline || !tagged.hasSyncBaseline)
82
+ })) return fail('instruction_set_sync_baseline_missing')
83
+ const changedDefaultKeys = new Set(loadedComponents.flatMap(component => (
84
+ component.modelConfig === undefined && component.hasSyncBaseline && component.changedSinceSync
85
+ ? [component.key]
86
+ : []
87
+ )))
88
+ const changedSameIdentityKeys = new Set(loadedComponents.flatMap(component => (
89
+ component.modelConfig === defaultIdentity && component.hasSyncBaseline && component.changedSinceSync
90
+ ? [component.key]
91
+ : []
92
+ )))
93
+ if ([...changedDefaultKeys].some(key => changedSameIdentityKeys.has(key))) return fail('instruction_set_conflicting_synced_edits')
94
+ if (changedDefaultKeys.size > 0) {
95
+ components = loadedComponents.filter(component => (
96
+ component.modelConfig !== defaultIdentity || !changedDefaultKeys.has(component.key)
97
+ ))
98
+ }
99
+ }
100
+ const namedProfiles = new Set([
101
+ ...(components.some(component => component.modelConfig === undefined) && defaultIdentity
102
+ ? [defaultIdentity]
103
+ : []),
104
+ ...components.flatMap(component => component.modelConfig ? [component.modelConfig] : []),
105
+ ])
106
+ const namedProfileHasPins = Array.isArray(input.profiles) && input.profiles.some(profile => {
107
+ if (!profile || typeof profile !== 'object') return false
108
+ const value = profile as Record<string, unknown>
109
+ return typeof value.modelConfigIdentity === 'string'
110
+ && namedProfiles.has(value.modelConfigIdentity)
111
+ && hasPinnedComponents(value.production)
112
+ })
113
+ if (namedProfileHasPins) return fail('instruction_set_component_unavailable')
114
+ return {
115
+ name: input.name,
116
+ ...(hasDescription ? { description } : {}),
117
+ shape: input.shape as string[],
118
+ components: components.map(({ key, modelConfig, text }) => ({ key, ...(modelConfig === undefined ? {} : { modelConfig }), text })),
119
+ }
120
+ }