orizu 0.5.21 → 0.5.23

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 (87) hide show
  1. package/README.md +46 -15
  2. package/dist/cleanup.d.ts +6 -0
  3. package/dist/cleanup.d.ts.map +1 -0
  4. package/dist/cleanup.js +27 -0
  5. package/dist/gepa-engine-dispatch.d.ts.map +1 -1
  6. package/dist/gepa-engine-dispatch.js +20 -1
  7. package/dist/gepa-python-paths.d.ts.map +1 -1
  8. package/dist/gepa-python-paths.js +25 -6
  9. package/dist/help.d.ts.map +1 -1
  10. package/dist/help.js +37 -15
  11. package/dist/hosted-commands.d.ts +13 -0
  12. package/dist/hosted-commands.d.ts.map +1 -0
  13. package/dist/hosted-commands.js +10 -0
  14. package/dist/hosted-gepa-cli.d.ts +11 -0
  15. package/dist/hosted-gepa-cli.d.ts.map +1 -0
  16. package/dist/hosted-gepa-cli.js +110 -0
  17. package/dist/hosted-gepa-numeric-values.d.ts +3 -0
  18. package/dist/hosted-gepa-numeric-values.d.ts.map +1 -0
  19. package/dist/hosted-gepa-numeric-values.js +49 -0
  20. package/dist/hosted-provider-settings.d.ts +3 -0
  21. package/dist/hosted-provider-settings.d.ts.map +1 -0
  22. package/dist/hosted-provider-settings.js +69 -0
  23. package/dist/hosted-session-cli.d.ts.map +1 -1
  24. package/dist/hosted-session-cli.js +6 -1
  25. package/dist/http.d.ts +1 -0
  26. package/dist/http.d.ts.map +1 -1
  27. package/dist/http.js +7 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +72 -74
  30. package/dist/instruction-set-loader.d.ts.map +1 -1
  31. package/dist/instruction-set-loader.js +57 -5
  32. package/dist/instruction-sets-cli.d.ts +5 -2
  33. package/dist/instruction-sets-cli.d.ts.map +1 -1
  34. package/dist/instruction-sets-cli.js +134 -24
  35. package/dist/optimizations-promote-cli.d.ts +3 -0
  36. package/dist/optimizations-promote-cli.d.ts.map +1 -1
  37. package/dist/optimizations-promote-cli.js +75 -7
  38. package/dist/runner-dir-verify.d.ts.map +1 -1
  39. package/dist/runner-dir-verify.js +4 -7
  40. package/dist/skilled-proposer-bake-report.d.ts +2 -0
  41. package/dist/skilled-proposer-bake-report.d.ts.map +1 -0
  42. package/dist/skilled-proposer-bake-report.js +1 -0
  43. package/dist/skilled-proposer-config.d.ts +2 -0
  44. package/dist/skilled-proposer-config.d.ts.map +1 -0
  45. package/dist/skilled-proposer-config.js +212 -0
  46. package/dist/skilled-proposer-launch.d.ts +26 -0
  47. package/dist/skilled-proposer-launch.d.ts.map +1 -1
  48. package/dist/skilled-proposer-launch.js +103 -6
  49. package/package.json +1 -1
  50. package/scripts/vendor-gepa-python.mjs +81 -12
  51. package/src/cleanup.ts +37 -0
  52. package/src/gepa-engine-dispatch.ts +21 -1
  53. package/src/gepa-python-paths.ts +24 -6
  54. package/src/help.ts +37 -15
  55. package/src/hosted-commands.ts +4 -0
  56. package/src/hosted-gepa-cli.ts +107 -0
  57. package/src/hosted-gepa-numeric-values.ts +60 -0
  58. package/src/hosted-provider-settings.ts +69 -0
  59. package/src/hosted-session-cli.ts +4 -1
  60. package/src/http.ts +8 -0
  61. package/src/index.ts +112 -89
  62. package/src/instruction-set-loader.ts +55 -5
  63. package/src/instruction-sets-cli.ts +173 -19
  64. package/src/optimizations-promote-cli.ts +85 -7
  65. package/src/runner-dir-verify.ts +11 -6
  66. package/src/skilled-proposer-bake-report.ts +1 -0
  67. package/src/skilled-proposer-config.ts +191 -0
  68. package/src/skilled-proposer-launch.ts +96 -7
  69. package/src/skilled-proposer-venv-manager.mjs +44 -27
  70. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +36 -3
  71. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +31 -3
  72. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +7 -2
  73. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +354 -5
  74. package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
  75. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +15 -0
  76. package/vendor/skills/orizu-cli/SKILL.md +94 -168
  77. package/vendor/skills/orizu-cli/references/assess-and-plan.md +107 -0
  78. package/vendor/skills/orizu-cli/references/building-judges.md +442 -180
  79. package/vendor/skills/orizu-cli/references/cli-reference.md +69 -39
  80. package/vendor/skills/orizu-cli/references/dataset-design.md +100 -0
  81. package/vendor/skills/orizu-cli/references/eval-strategy.md +64 -0
  82. package/vendor/skills/orizu-cli/references/instructions-after-prompts.md +115 -0
  83. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +150 -82
  84. package/vendor/skills/orizu-cli/references/optimization-reports.md +196 -104
  85. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +57 -37
  86. package/vendor/skills/orizu-cli/references/primer.md +43 -18
  87. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +199 -131
@@ -0,0 +1,60 @@
1
+ interface NumericRule {
2
+ field: string
3
+ flag: string
4
+ grammar: string
5
+ accepts: (value: unknown) => boolean
6
+ }
7
+
8
+ const isFiniteNumber = (value: unknown): value is number =>
9
+ typeof value === 'number' && Number.isFinite(value)
10
+ const isInteger = (value: unknown): value is number =>
11
+ isFiniteNumber(value) && Number.isInteger(value)
12
+ const isPositiveInteger = (value: unknown): value is number =>
13
+ isInteger(value) && value > 0
14
+
15
+ const rules: NumericRule[] = [
16
+ ...[
17
+ ['proposalMaxCalls', '--proposal-max-calls'],
18
+ ['proposalMaxTokens', '--proposal-max-tokens'],
19
+ ['maxIterations', '--max-iterations'],
20
+ ['maxCandidateProposals', '--max-candidate-proposals'],
21
+ ['maxFullEvals', '--max-full-evals'],
22
+ ['maxMetricCalls', '--max-metric-calls'],
23
+ ['minibatchSize', '--minibatch-size'],
24
+ ['reflectionMaxTokens', '--reflection-max-tokens'],
25
+ ['reflectionRetryAttempts', '--reflection-retry-attempts'],
26
+ ['reflectionHttpTimeoutSeconds', '--reflection-http-timeout-seconds'],
27
+ ].map(([field, flag]) => ({ field, flag, grammar: 'positive integer', accepts: isPositiveInteger })),
28
+ {
29
+ field: 'numThreads', flag: '--num-threads', grammar: 'auto or positive integer',
30
+ accepts: value => value === 'auto' || isPositiveInteger(value),
31
+ },
32
+ ...[
33
+ ['epsilon', '--epsilon'],
34
+ ['reflectionTemperature', '--reflection-temperature'],
35
+ ].map(([field, flag]) => ({ field, flag, grammar: 'finite number', accepts: isFiniteNumber })),
36
+ { field: 'seed', flag: '--seed', grammar: 'integer', accepts: isInteger },
37
+ ]
38
+
39
+ const integerPattern = /^[+-]?\d+$/
40
+ const floatPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i
41
+
42
+ export function parseHostedGepaNumericValue(field: string, rawValue: string): number | 'auto' {
43
+ const rule = rules.find(candidate => candidate.field === field)
44
+ if (!rule) throw new Error(`Unknown hosted numeric field ${field}`)
45
+ const trimmed = rawValue.trim()
46
+ if (field === 'numThreads' && trimmed.toLowerCase() === 'auto') return 'auto'
47
+ const lexicalPattern = rule.grammar === 'finite number' ? floatPattern : integerPattern
48
+ const parsed = lexicalPattern.test(trimmed) ? Number(trimmed) : Number.NaN
49
+ if (!rule.accepts(parsed)) throw new Error(`${rule.flag} must be ${rule.grammar}`)
50
+ return parsed
51
+ }
52
+
53
+ export function hostedGepaNumericError(values: Record<string, unknown>): string | null {
54
+ for (const rule of rules) {
55
+ if (values[rule.field] !== undefined && !rule.accepts(values[rule.field])) {
56
+ return `${rule.field} must be ${rule.grammar}`
57
+ }
58
+ }
59
+ return null
60
+ }
@@ -0,0 +1,69 @@
1
+ function isRecord(value: unknown): value is Record<string, unknown> {
2
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
3
+ }
4
+
5
+ function hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
6
+ return Object.keys(value).every(key => keys.includes(key))
7
+ }
8
+
9
+ function isChoice(value: unknown, choices: readonly string[]): boolean {
10
+ return typeof value === 'string' && choices.includes(value)
11
+ }
12
+
13
+ function isProviderObject(value: unknown, keys: readonly string[], choices: Readonly<Record<string, readonly string[]>>): boolean {
14
+ return isRecord(value) && hasOnlyKeys(value, keys)
15
+ && Object.entries(value).every(([key, nested]) => isChoice(nested, choices[key] ?? []))
16
+ }
17
+
18
+ function isCredentialKey(key: string): boolean {
19
+ const tokens = key.replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)
20
+ return tokens.some(token => ['auth', 'authorization', 'token', 'secret', 'password', 'passwd', 'credential', 'credentials', 'key'].includes(token))
21
+ || /(?:authorization|authheader|bearer|token|secret|password|passwd|credential|credentials|apikey|accesskey(?:id)?|privatekey|clientkey|secretkey|connection(?:string|url)|dsn)$/.test(tokens.join(''))
22
+ }
23
+
24
+ function containsCredential(value: unknown, root = true): boolean {
25
+ if (Array.isArray(value)) return root || value.some(nested => containsCredential(nested, false))
26
+ if (!isRecord(value)) return root ? value !== undefined
27
+ : typeof value === 'string' && (/(?:authorization|bearer|x-api-key|x-auth-token)\s*[: ]/i.test(value)
28
+ || /^[a-z]+:\/\/[^/@]*:[^/@]*@/i.test(value))
29
+ return Object.entries(value).some(([key, nested]) => isCredentialKey(key) || containsCredential(nested, false))
30
+ }
31
+
32
+ function unsupportedHostedSetting(value: unknown, model: string): string | null {
33
+ if (value === undefined) return null
34
+ if (!isRecord(value)) return '<root>'
35
+ const providerKeys = model.startsWith('openai/') ? ['reasoning']
36
+ : model.startsWith('anthropic/') ? ['thinking', 'output_config'] : []
37
+ const unknown = Object.keys(value).find(key => !['top_p', ...providerKeys].includes(key))
38
+ if (unknown) return unknown
39
+ if (value.top_p !== undefined && (typeof value.top_p !== 'number'
40
+ || !Number.isFinite(value.top_p) || value.top_p < 0 || value.top_p > 1)) return 'top_p'
41
+ if (value.thinking !== undefined && (!isRecord(value.thinking) || value.thinking.type === undefined)) return 'thinking.type'
42
+ for (const [key, nested, keys, choices] of [
43
+ ['thinking', value.thinking, ['type'], { type: ['adaptive'] }],
44
+ ['output_config', value.output_config, ['effort'], { effort: ['low', 'medium', 'high', 'max'] }],
45
+ ['reasoning', value.reasoning, ['effort', 'summary'], { effort: ['minimal', 'low', 'medium', 'high', 'xhigh'], summary: ['auto', 'concise', 'detailed'] }],
46
+ ] as Array<[string, unknown, readonly string[], Readonly<Record<string, readonly string[]>>]>) if (nested !== undefined && !isProviderObject(nested, keys, choices)) {
47
+ if (!isRecord(nested)) return key
48
+ return `${key}.${Object.keys(nested).find(child => !keys.includes(child)
49
+ || !isChoice(nested[child], choices[child] ?? [])) ?? key}`
50
+ }
51
+ return null
52
+ }
53
+
54
+ export function hostedSettingsContainCredentials(value: unknown): boolean { return containsCredential(value) }
55
+
56
+ export function hostedProviderSettingsError(
57
+ value: unknown, model = 'anthropic/', temperature?: unknown
58
+ ): string | null {
59
+ if (hostedSettingsContainCredentials(value)) {
60
+ return 'Hosted reflection provider settings must not contain credential-bearing fields.'
61
+ }
62
+ const unsupported = unsupportedHostedSetting(value, model)
63
+ if (unsupported) return `Unsupported hosted setting "${unsupported}".`
64
+ if (model.startsWith('anthropic/') && temperature !== undefined && temperature !== null
65
+ && isRecord(value) && value.thinking !== undefined) {
66
+ return 'reflection_temperature cannot be combined with Anthropic thinking'
67
+ }
68
+ return null
69
+ }
@@ -57,6 +57,7 @@ import { buildEgressPolicy, DEFAULT_EGRESS_CANARY_HOST } from './egress-policy.j
57
57
  import { DEFAULT_HOSTED_MODEL, hostedLoopCommand, type HostedLoopContext } from './hosted-loop.js'
58
58
  import { hostedBootCommand } from './hosted-boot.js'
59
59
  import { mergeJobCommand } from './merge-job.js'
60
+ import { skilledProposerBakeCommand } from './skilled-proposer-launch.js'
60
61
  import { authedFetch } from './http.js'
61
62
  import { resolveBaseUrl } from './http.js'
62
63
  import { findUnknownOption } from './option-validation.js'
@@ -1568,7 +1569,9 @@ export async function hostedCommand(
1568
1569
  if (positional[1] === 'merge-job') {
1569
1570
  return mergeJobCommand(io)
1570
1571
  }
1571
- io.printErr?.('Usage: orizu internal <hosted-loop --context <path> | hosted-boot | merge-job>')
1572
+ if (positional[1] === 'bake-skilled-proposer-venv') return skilledProposerBakeCommand(io)
1573
+ if (positional[1] === 'verify-skilled-proposer-bake') return skilledProposerBakeCommand(io, true)
1574
+ io.printErr?.('Usage: orizu internal <hosted-loop --context <path> | hosted-boot | merge-job | bake-skilled-proposer-venv | verify-skilled-proposer-bake>')
1572
1575
  return 1
1573
1576
  }
1574
1577
 
package/src/http.ts CHANGED
@@ -93,6 +93,10 @@ function isExpired(expiresAt: number): boolean {
93
93
  return expiresAt <= nowUnix + 30
94
94
  }
95
95
 
96
+ export function credentialRequestRedirectPolicy(): Pick<RequestInit, 'redirect'> {
97
+ return process.env.ORIZU_CLI_AUTH_REDIRECT === 'error' ? { redirect: 'error' } : {}
98
+ }
99
+
96
100
  async function refreshCredentials(baseUrl: string, credentials: ServerCredentials): Promise<ServerCredentials> {
97
101
  if (!isSessionCredentials(credentials)) {
98
102
  throw new Error('API key credentials do not refresh. Run `orizu login` again if access fails.')
@@ -103,6 +107,7 @@ async function refreshCredentials(baseUrl: string, credentials: ServerCredential
103
107
  method: 'POST',
104
108
  headers: { 'Content-Type': 'application/json' },
105
109
  body: JSON.stringify({ refreshToken: credentials.refreshToken }),
110
+ ...credentialRequestRedirectPolicy(),
106
111
  })
107
112
 
108
113
  if (!response.ok) {
@@ -141,6 +146,7 @@ export async function authedFetch(path: string, init: RequestInit = {}) {
141
146
  ...(init.headers || {}),
142
147
  Authorization: `Bearer ${envBearer}`,
143
148
  },
149
+ ...credentialRequestRedirectPolicy(),
144
150
  })
145
151
  }
146
152
 
@@ -160,6 +166,7 @@ export async function authedFetch(path: string, init: RequestInit = {}) {
160
166
  ...(init.headers || {}),
161
167
  Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
162
168
  },
169
+ ...credentialRequestRedirectPolicy(),
163
170
  })
164
171
 
165
172
  if (response.status === 401 && isSessionCredentials(activeCredentials)) {
@@ -170,6 +177,7 @@ export async function authedFetch(path: string, init: RequestInit = {}) {
170
177
  ...(init.headers || {}),
171
178
  Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
172
179
  },
180
+ ...credentialRequestRedirectPolicy(),
173
181
  })
174
182
  }
175
183
 
package/src/index.ts CHANGED
@@ -36,7 +36,14 @@ import { parseGlobalFlags } from './global-flags.js'
36
36
  import { getCapabilities, renderHelpForArgs, renderRootHelp } from './help.js'
37
37
  import { runLocalAppPreview } from './preview-runtime.js'
38
38
  import { readAssignmentManifestJsonlFile } from './task-assignment-manifest.js'
39
- import { assertSecureTokenTransport, authedFetch, getBaseUrl, resolveLoginBaseUrl, setGlobalFlags } from './http.js'
39
+ import {
40
+ assertSecureTokenTransport,
41
+ authedFetch,
42
+ credentialRequestRedirectPolicy,
43
+ getBaseUrl,
44
+ resolveLoginBaseUrl,
45
+ setGlobalFlags,
46
+ } from './http.js'
40
47
  import {
41
48
  computeSkillContentHash,
42
49
  getSkillInstallPath,
@@ -71,6 +78,7 @@ import { ARTIFACT_MAX_BYTES, runnerOptimizerCommand } from './artifact-pull.js'
71
78
  import { killSwitchCommand } from './kill-switch-cli.js'
72
79
  import { egressAllowlistCommand } from './egress-allowlist-cli.js'
73
80
  import { acceptScoreRunCommand } from './scores-accept-cli.js'
81
+ import { cleanupAll, throwWithCleanup } from './cleanup.js'
74
82
  import { listOptimizationRunsCommand } from './optimizations-list-cli.js'
75
83
  import { promoteOptimizationCommand } from './optimizations-promote-cli.js'
76
84
  import { scorersCommand } from './scorers-cli.js'
@@ -86,7 +94,7 @@ import {
86
94
  import { teamConnectorsCommand } from './team-connectors-cli.js'
87
95
  import { manifestsCommand } from './manifests-cli.js'
88
96
  import { workbenchCommand } from './workbench-cli.js'
89
- import { hostedCommand } from './hosted-session-cli.js'
97
+ import { dispatchHostedCommands } from './hosted-commands.js'
90
98
  import { workspaceSyncCommand } from './workspace-sync.js'
91
99
  import { runGitCredentialInvocation } from './git-credential.js'
92
100
  import { instructionSetsCommand, syncToDisk } from './instruction-sets-cli.js'; export { loadInstructionSet } from './instruction-set-loader.js'
@@ -100,7 +108,13 @@ import type {
100
108
  CliLengthMeasurementUnavailableReason,
101
109
  CliLengthStats,
102
110
  } from './prompt-length-wire.js'
103
- import { assertSnapshotManifestConfined, verifyGepaRunnerDirsFromArgs, verifyRunnerDirRegistered } from './runner-dir-verify.js'
111
+ import {
112
+ assertSnapshotManifestConfined,
113
+ extractFlagValue,
114
+ type GepaVerifiedArgs,
115
+ verifyGepaRunnerDirsFromArgs,
116
+ verifyRunnerDirRegistered,
117
+ } from './runner-dir-verify.js'
104
118
  import { runScorersRegister } from './scorer-draft-push.js'
105
119
  import { runZipArtifactPush } from './zip-draft-push.js'
106
120
  import { type GithubLinkResult, runGithubLink, runInteractiveHostedSetup } from './github-setup.js'
@@ -117,7 +131,7 @@ import { dispatchGepaEngine } from './gepa-engine-dispatch.js'
117
131
  import { resolveGepaInstructionSetProfileVersion } from './instruction-set-gepa-launch.js'
118
132
  import { getGepaPythonCommand } from './gepa-python-command.js'
119
133
  import { getGepaPythonPathEntries } from './gepa-python-paths.js'
120
- import { prepareSkilledProposerLaunch } from './skilled-proposer-launch.js'
134
+ import { prepareSkilledProposerLaunch, spawnSkilledProposerChild } from './skilled-proposer-launch.js'
121
135
 
122
136
  export { parseJsonResponse, sanitizeTerminalText } from './json-response.js'
123
137
 
@@ -1287,6 +1301,7 @@ async function logout() {
1287
1301
  body: 'refreshToken' in credentials
1288
1302
  ? JSON.stringify({ refreshToken: credentials.refreshToken })
1289
1303
  : undefined,
1304
+ ...credentialRequestRedirectPolicy(),
1290
1305
  })
1291
1306
 
1292
1307
  if (!response.ok) {
@@ -1767,53 +1782,6 @@ function refusePromptMutation(replacement: string): never {
1767
1782
  throw new Error(`Use: orizu instructions ${replacement}`)
1768
1783
  }
1769
1784
 
1770
- async function bindPromptScorer(role: 'headline' | 'tracked') {
1771
- const promptId = getPositionalArg(3)
1772
- const project = getArg('--project') || await resolveProjectSlug(null)
1773
- const scorerVersionId = getArg('--scorer-version')
1774
-
1775
- if (!promptId || !scorerVersionId) {
1776
- throw new Error(`Usage: orizu prompts scorers ${role === 'headline' ? 'set-headline' : 'add'} <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]`)
1777
- }
1778
-
1779
- const response = await authedFetch(`/api/cli/prompts/scorers?project=${encodeURIComponent(project)}`, {
1780
- method: 'POST',
1781
- headers: { 'Content-Type': 'application/json' },
1782
- body: JSON.stringify({
1783
- promptId,
1784
- scorerVersionId,
1785
- role,
1786
- datasetVersionId: getArg('--dataset-version') || undefined,
1787
- splitSetId: getArg('--split-set') || undefined,
1788
- splitName: getArg('--split') || undefined,
1789
- }),
1790
- })
1791
-
1792
- if (!response.ok) {
1793
- throw new Error(`Failed to bind prompt scorer: ${await response.text()}`)
1794
- }
1795
-
1796
- const data = await parseJsonResponse<{
1797
- promptScorer: Record<string, unknown>
1798
- owner?: {
1799
- instructionSetSlug: string
1800
- componentKey: string
1801
- }
1802
- }>(response, 'Prompt scorer bind')
1803
- if (hasJsonFlag()) {
1804
- printJson(data)
1805
- return
1806
- }
1807
-
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
- }
1815
- }
1816
-
1817
1785
  function getPositionalArg(index: number): string | null {
1818
1786
  const value = cliArgs[index]
1819
1787
  return value && !value.startsWith('--') ? value : null
@@ -2497,6 +2465,43 @@ async function updateOptimizationRunLifecycle(action: OptimizationLifecycleActio
2497
2465
 
2498
2466
  // ALI-1073 list moved to optimizations-list-cli.ts (line ratchet, ALI-976);
2499
2467
  // ALI-1175 labels its best scores as agent-reported evidence there.
2468
+ const GEPA_RUNNER_MATERIALIZATION_PAIRS = [
2469
+ { versionFlag: '--runner-version-id', dirFlag: '--candidate-runner-dir' },
2470
+ { versionFlag: '--scorer-runner-version-id', dirFlag: '--scorer-runner-dir' },
2471
+ ] as const
2472
+
2473
+ async function materializeMissingGepaRunnerDirs(args: string[]): Promise<{
2474
+ args: string[]
2475
+ cleanup: () => void
2476
+ }> {
2477
+ const rewritten = [...args]
2478
+ const cleanups: Array<() => void> = []
2479
+ const cleanup = () => cleanupAll(
2480
+ cleanups,
2481
+ 'Failed to clean materialized runner directories'
2482
+ )
2483
+
2484
+ try {
2485
+ for (const pair of GEPA_RUNNER_MATERIALIZATION_PAIRS) {
2486
+ const runnerVersionId = extractFlagValue(rewritten, pair.versionFlag)
2487
+ const runnerDir = extractFlagValue(rewritten, pair.dirFlag)
2488
+ if (!runnerVersionId || runnerDir) continue
2489
+
2490
+ const materialized = await materializeRunnerVersion(runnerVersionId)
2491
+ cleanups.push(materialized.cleanup)
2492
+ rewritten.push(pair.dirFlag, materialized.runnerDir)
2493
+ }
2494
+ } catch (error) {
2495
+ throwWithCleanup(
2496
+ error,
2497
+ cleanups,
2498
+ 'Runner materialization failed and cleanup also failed'
2499
+ )
2500
+ }
2501
+
2502
+ return { args: rewritten, cleanup }
2503
+ }
2504
+
2500
2505
  async function runGepaOptimization() {
2501
2506
  const project = getArg('--project') || await resolveProjectSlug(null)
2502
2507
  const baseUrl = getBaseUrl()
@@ -2509,11 +2514,12 @@ async function runGepaOptimization() {
2509
2514
  // itself (both argparse flag forms), fail-closed on half-populated pairs,
2510
2515
  // and REWRITES the dir flags to verified snapshots (the exact hashed bytes)
2511
2516
  // so a post-hash mutation of the original dirs cannot change what runs.
2512
- const verified = await verifyGepaRunnerDirsFromArgs(forwardedArgs)
2513
- forwardedArgs = verified.args
2514
- let result
2517
+ const materialized = await materializeMissingGepaRunnerDirs(forwardedArgs)
2518
+ let verified: GepaVerifiedArgs | undefined
2515
2519
  let selectedEngine = 'official'
2516
2520
  try {
2521
+ verified = await verifyGepaRunnerDirsFromArgs(materialized.args)
2522
+ forwardedArgs = verified.args
2517
2523
  // Uniform resolution (ALI-1090): honors ORIZU_TOKEN / ORIZU_TOKEN_FILE before
2518
2524
  // credentials.json so hosted sandboxes can run optimizations. Read at spawn
2519
2525
  // time — AFTER the verification awaits above, so a hosted token-file
@@ -2539,18 +2545,29 @@ async function runGepaOptimization() {
2539
2545
  if (instructionSetProfileVersionId) dispatch.environment.ORIZU_INSTRUCTION_SET_PROFILE_VERSION_ID = instructionSetProfileVersionId
2540
2546
  selectedEngine = dispatch.engine
2541
2547
  const launch = prepareSkilledProposerLaunch(python, dispatch.engine, dispatch.environment)
2542
- result = spawnSync(launch.python, ['-m', dispatch.module, ...dispatch.args], { stdio: 'inherit', env: launch.environment })
2543
- } finally {
2544
- verified.cleanup()
2545
- }
2546
- if (result.error) {
2547
- throw new Error(`${selectedEngine} GEPA engine failed: ${result.error.message}`, { cause: result.error })
2548
- }
2549
-
2550
- if (result.status !== 0) {
2551
- throw new Error(`${selectedEngine} GEPA engine failed with exit code ${result.status}`)
2548
+ const result = await spawnSkilledProposerChild(
2549
+ launch.python, ['-m', dispatch.module, ...dispatch.args], dispatch.engine, launch.environment,
2550
+ )
2551
+ if (result.error) {
2552
+ throw new Error(`${selectedEngine} GEPA engine failed: ${result.error.message}`, { cause: result.error })
2553
+ }
2554
+ if (result.status !== 0) {
2555
+ throw new Error(`${selectedEngine} GEPA engine failed with exit code ${result.status}`)
2556
+ }
2557
+ } catch (error) {
2558
+ throwWithCleanup(
2559
+ error,
2560
+ [
2561
+ ...(verified ? [verified.cleanup] : []),
2562
+ materialized.cleanup,
2563
+ ],
2564
+ 'GEPA launch failed and runner cleanup also failed'
2565
+ )
2552
2566
  }
2553
-
2567
+ cleanupAll(
2568
+ [verified.cleanup, materialized.cleanup],
2569
+ 'Failed to clean GEPA runner directories'
2570
+ )
2554
2571
  if (hasJsonFlag()) {
2555
2572
  printJson({ status: 'completed', exitCode: 0 })
2556
2573
  }
@@ -5829,26 +5846,33 @@ export async function materializeRunnerVersion(runnerVersionId: string): Promise
5829
5846
  const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-version-'))
5830
5847
  const zipPath = join(tempDir, 'runner.zip')
5831
5848
  const runnerDir = join(tempDir, 'runner')
5832
- const zipBytes = new Uint8Array(await response.arrayBuffer())
5833
- if (zipBytes.byteLength > ARTIFACT_MAX_BYTES) {
5834
- rmSync(tempDir, { recursive: true, force: true })
5835
- throw new Error(`Runner artifact exceeds ${ARTIFACT_MAX_BYTES} bytes`)
5836
- }
5837
- writeFileSync(zipPath, zipBytes)
5849
+ const cleanup = () => rmSync(tempDir, { recursive: true, force: true })
5838
5850
 
5839
- const result = spawnSync('unzip', ['-q', zipPath, '-d', runnerDir], {
5840
- encoding: 'utf8',
5841
- })
5842
- if (result.error) {
5843
- rmSync(tempDir, { recursive: true, force: true })
5844
- throw result.error
5845
- }
5846
- if (result.status !== 0) {
5847
- rmSync(tempDir, { recursive: true, force: true })
5848
- throw new Error(`unzip failed: ${sanitizeTerminalText(result.stderr || result.stdout || '')}`)
5849
- }
5851
+ try {
5852
+ const zipBytes = new Uint8Array(await response.arrayBuffer())
5853
+ if (zipBytes.byteLength > ARTIFACT_MAX_BYTES) {
5854
+ throw new Error(`Runner artifact exceeds ${ARTIFACT_MAX_BYTES} bytes`)
5855
+ }
5856
+ writeFileSync(zipPath, zipBytes)
5850
5857
 
5851
- return { runnerDir, cleanup: () => rmSync(tempDir, { recursive: true, force: true }) }
5858
+ const result = spawnSync('unzip', ['-q', zipPath, '-d', runnerDir], {
5859
+ encoding: 'utf8',
5860
+ })
5861
+ if (result.error) {
5862
+ throw result.error
5863
+ }
5864
+ if (result.status !== 0) {
5865
+ throw new Error(`unzip failed: ${sanitizeTerminalText(result.stderr || result.stdout || '')}`)
5866
+ }
5867
+
5868
+ return { runnerDir, cleanup }
5869
+ } catch (error) {
5870
+ throwWithCleanup(
5871
+ error,
5872
+ [cleanup],
5873
+ `Runner ${runnerVersionId} materialization failed and cleanup also failed`
5874
+ )
5875
+ }
5852
5876
  }
5853
5877
 
5854
5878
  async function runnersExec() {
@@ -6236,13 +6260,11 @@ export async function main(rawArgs = process.argv.slice(2)) {
6236
6260
  }
6237
6261
 
6238
6262
  if (command === 'prompts' && subcommand === 'scorers' && cliArgs[2] === 'set-headline') {
6239
- await bindPromptScorer('headline')
6240
- return
6263
+ refusePromptMutation('scorers set-headline <set> --key <component-key> --scorer-version <id> --project <team/project>')
6241
6264
  }
6242
6265
 
6243
6266
  if (command === 'prompts' && subcommand === 'scorers' && cliArgs[2] === 'add') {
6244
- await bindPromptScorer('tracked')
6245
- return
6267
+ refusePromptMutation('scorers add <set> --key <component-key> --scorer-version <id> --project <team/project>')
6246
6268
  }
6247
6269
 
6248
6270
  if (command === 'judges' && subcommand === 'list') {
@@ -6324,6 +6346,7 @@ export async function main(rawArgs = process.argv.slice(2)) {
6324
6346
  return
6325
6347
  }
6326
6348
 
6349
+ if (await dispatchHostedCommands(cliArgs, { environment: process.env, getArg, json: hasJsonFlag(), printJson, printLine, printErr: printError, resolveProjectSlug, setExitCode: code => { process.exitCode = code } })) return
6327
6350
  if (command === 'optimizations' && subcommand === 'start') {
6328
6351
  await startOptimizationRun()
6329
6352
  return
@@ -6355,7 +6378,8 @@ export async function main(rawArgs = process.argv.slice(2)) {
6355
6378
  }
6356
6379
  if (command === 'optimizations' && subcommand === 'promote') {
6357
6380
  await promoteOptimizationCommand(cliArgs.slice(2), {
6358
- json: hasJsonFlag(), print: printLine, resolveProjectSlug, fetcher: authedFetch,
6381
+ json: hasJsonFlag(), origin: parsed.flags, print: printLine, printErr: printError,
6382
+ resolveProjectSlug, fetcher: authedFetch,
6359
6383
  })
6360
6384
  return
6361
6385
  }
@@ -6511,7 +6535,6 @@ export async function main(rawArgs = process.argv.slice(2)) {
6511
6535
  if (command === 'datasets' && subcommand === 'lock') { await lockDataset(); return }
6512
6536
  if (command === 'datasets' && subcommand === 'clone') { await cloneDataset(); return }
6513
6537
  if (command === 'tasks' && subcommand === 'export') { await downloadAnnotations(); return }
6514
- if (command === 'internal' || (command === 'session' && subcommand === 'start' && hasArg('--hosted'))) { process.exitCode = await hostedCommand(cliArgs, { json: hasJsonFlag(), print: printLine, printErr: printError }); return }
6515
6538
  if (command === 'session' || command === 'run') {
6516
6539
  process.exitCode = await workbenchCommand(cliArgs, { json: hasJsonFlag(), print: printLine })
6517
6540
  return
@@ -1,4 +1,4 @@
1
- import { existsSync, realpathSync, readFileSync, statSync } from 'node:fs'
1
+ import { existsSync, realpathSync, readFileSync, readdirSync, statSync } from 'node:fs'
2
2
  import { resolve, sep } from 'node:path'
3
3
 
4
4
  export class InstructionSetLoaderError extends Error {
@@ -6,14 +6,64 @@ export class InstructionSetLoaderError extends Error {
6
6
  }
7
7
 
8
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[] }
9
+ interface Manifest { manifestVersion: number; projectId?: string; instructionSetId?: string; name: string; slug?: string; shape: string[]; default: Material; profiles: Array<{ modelConfigIdentity: string; production: Material | null }>; filteredTo?: string[] }
10
10
 
11
11
  function inside(root: string, candidate: string) { return candidate === root || candidate.startsWith(`${root}${sep}`) }
12
12
 
13
+ function manifestMatchesReference(candidate: string, reference: string): boolean {
14
+ try {
15
+ const manifest = JSON.parse(
16
+ readFileSync(resolve(candidate, 'manifest.json'), 'utf8')
17
+ ) as Pick<Manifest, 'name' | 'slug'>
18
+ return manifest.name === reference || manifest.slug === reference
19
+ } catch {
20
+ return false
21
+ }
22
+ }
23
+
24
+ function resolveSetRoot(directory: string, reference: string): string {
25
+ let directoryRoot: string
26
+ try { directoryRoot = realpathSync(directory) } catch { throw new InstructionSetLoaderError('instruction_set_not_synced') }
27
+ const direct = resolve(directoryRoot, reference)
28
+ try {
29
+ if (inside(directoryRoot, direct) && existsSync(direct) && statSync(direct).isDirectory()) {
30
+ const realDirect = realpathSync(direct)
31
+ if (inside(directoryRoot, realDirect)) {
32
+ try {
33
+ const manifest = JSON.parse(
34
+ readFileSync(resolve(realDirect, 'manifest.json'), 'utf8')
35
+ ) as Pick<Manifest, 'name' | 'slug'>
36
+ if (manifest.name === reference || manifest.slug === reference) {
37
+ return realDirect
38
+ }
39
+ } catch {
40
+ // Preserve the exact-path loader's specific missing/invalid manifest error.
41
+ return realDirect
42
+ }
43
+ }
44
+ }
45
+ } catch {
46
+ // A stale or unreadable direct-path candidate must not shadow a valid set.
47
+ }
48
+ const matches = readdirSync(directoryRoot, { withFileTypes: true }).flatMap(entry => {
49
+ try {
50
+ if (entry.name.startsWith('.') || !entry.isDirectory()) return []
51
+ const candidate = realpathSync(resolve(directoryRoot, entry.name))
52
+ return inside(directoryRoot, candidate) && manifestMatchesReference(candidate, reference)
53
+ ? [candidate]
54
+ : []
55
+ } catch {
56
+ return []
57
+ }
58
+ })
59
+ if (matches.length !== 1) throw new InstructionSetLoaderError(
60
+ matches.length > 1 ? 'instruction_set_reference_ambiguous' : 'instruction_set_not_synced'
61
+ )
62
+ return matches[0]!
63
+ }
64
+
13
65
  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)
66
+ const setRoot = resolveSetRoot(directory, name)
17
67
  const manifestPath = resolve(setRoot, 'manifest.json')
18
68
  if (!existsSync(manifestPath)) throw new InstructionSetLoaderError('instruction_set_manifest_missing')
19
69
  let manifest: Manifest