orizu 0.5.26 → 0.5.28

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 (164) hide show
  1. package/README.md +19 -0
  2. package/dist/artifacts-git-runtime.d.ts +34 -0
  3. package/dist/artifacts-git-runtime.d.ts.map +1 -0
  4. package/dist/artifacts-git-runtime.js +312 -0
  5. package/dist/cloudflare-artifacts-git-remote.d.ts +5 -0
  6. package/dist/cloudflare-artifacts-git-remote.d.ts.map +1 -0
  7. package/dist/cloudflare-artifacts-git-remote.js +21 -0
  8. package/dist/dataset-transfer-download.d.ts +2 -0
  9. package/dist/dataset-transfer-download.d.ts.map +1 -0
  10. package/dist/dataset-transfer-download.js +66 -0
  11. package/dist/dataset-transfer-policy.d.ts +2 -0
  12. package/dist/dataset-transfer-policy.d.ts.map +1 -0
  13. package/dist/dataset-transfer-policy.js +8 -0
  14. package/dist/dataset-transfer-upload.d.ts +42 -0
  15. package/dist/dataset-transfer-upload.d.ts.map +1 -0
  16. package/dist/dataset-transfer-upload.js +110 -0
  17. package/dist/git-credential.d.ts.map +1 -1
  18. package/dist/git-credential.js +1 -1
  19. package/dist/git-recovery-environment.d.ts +1 -0
  20. package/dist/git-recovery-environment.d.ts.map +1 -1
  21. package/dist/git-recovery-environment.js +1 -1
  22. package/dist/help.d.ts.map +1 -1
  23. package/dist/help.js +44 -11
  24. package/dist/hosted-boot.d.ts +75 -2
  25. package/dist/hosted-boot.d.ts.map +1 -1
  26. package/dist/hosted-boot.js +585 -102
  27. package/dist/hosted-bootstrap.d.ts +2 -0
  28. package/dist/hosted-bootstrap.d.ts.map +1 -1
  29. package/dist/hosted-bootstrap.js +12 -3
  30. package/dist/hosted-harness-opencode.d.ts +8 -9
  31. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  32. package/dist/hosted-harness-opencode.js +126 -24
  33. package/dist/hosted-harness.d.ts +3 -0
  34. package/dist/hosted-harness.d.ts.map +1 -1
  35. package/dist/hosted-harness.js +4 -1
  36. package/dist/hosted-harvest.d.ts +30 -9
  37. package/dist/hosted-harvest.d.ts.map +1 -1
  38. package/dist/hosted-harvest.js +222 -28
  39. package/dist/hosted-loop-lifecycle.d.ts +15 -3
  40. package/dist/hosted-loop-lifecycle.d.ts.map +1 -1
  41. package/dist/hosted-loop-lifecycle.js +6 -4
  42. package/dist/hosted-loop.d.ts +47 -7
  43. package/dist/hosted-loop.d.ts.map +1 -1
  44. package/dist/hosted-loop.js +310 -64
  45. package/dist/hosted-question-limits.d.ts +7 -0
  46. package/dist/hosted-question-limits.d.ts.map +1 -0
  47. package/dist/hosted-question-limits.js +6 -0
  48. package/dist/hosted-question.d.ts +27 -0
  49. package/dist/hosted-question.d.ts.map +1 -0
  50. package/dist/hosted-question.js +118 -0
  51. package/dist/hosted-run-event-sink.d.ts +10 -5
  52. package/dist/hosted-run-event-sink.d.ts.map +1 -1
  53. package/dist/hosted-run-event-sink.js +12 -8
  54. package/dist/hosted-runtime-assets.d.ts +16 -7
  55. package/dist/hosted-runtime-assets.d.ts.map +1 -1
  56. package/dist/hosted-runtime-assets.js +40 -12
  57. package/dist/hosted-session-cli.d.ts.map +1 -1
  58. package/dist/hosted-session-cli.js +10 -19
  59. package/dist/http.d.ts +1 -1
  60. package/dist/http.d.ts.map +1 -1
  61. package/dist/http.js +10 -4
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +113 -92
  64. package/dist/instruction-set-gepa-launch.d.ts +1 -1
  65. package/dist/instruction-set-gepa-launch.d.ts.map +1 -1
  66. package/dist/instruction-set-gepa-launch.js +5 -3
  67. package/dist/instruction-set-loader.d.ts.map +1 -1
  68. package/dist/instruction-set-loader.js +63 -2
  69. package/dist/instruction-set-lock/index.d.ts +51 -0
  70. package/dist/instruction-set-lock/index.d.ts.map +1 -0
  71. package/dist/instruction-set-lock/index.js +459 -0
  72. package/dist/instruction-set-lock/schema.v1.d.ts +136 -0
  73. package/dist/instruction-set-lock/schema.v1.d.ts.map +1 -0
  74. package/dist/instruction-set-lock/schema.v1.js +155 -0
  75. package/dist/instruction-set-provenance.d.ts +26 -0
  76. package/dist/instruction-set-provenance.d.ts.map +1 -0
  77. package/dist/instruction-set-provenance.js +68 -0
  78. package/dist/instruction-set-sync/helpers.d.ts +22 -0
  79. package/dist/instruction-set-sync/helpers.d.ts.map +1 -0
  80. package/dist/instruction-set-sync/helpers.js +677 -0
  81. package/dist/instruction-set-sync/index.d.ts +60 -0
  82. package/dist/instruction-set-sync/index.d.ts.map +1 -0
  83. package/dist/instruction-set-sync/index.js +689 -0
  84. package/dist/instruction-set-update/index.d.ts +42 -0
  85. package/dist/instruction-set-update/index.d.ts.map +1 -0
  86. package/dist/instruction-set-update/index.js +445 -0
  87. package/dist/instruction-set-verify/index.d.ts +27 -0
  88. package/dist/instruction-set-verify/index.d.ts.map +1 -0
  89. package/dist/instruction-set-verify/index.js +865 -0
  90. package/dist/instruction-sets-cli.d.ts +1 -4
  91. package/dist/instruction-sets-cli.d.ts.map +1 -1
  92. package/dist/instruction-sets-cli.js +220 -109
  93. package/dist/model-config-fields.d.ts +35 -0
  94. package/dist/model-config-fields.d.ts.map +1 -0
  95. package/dist/model-config-fields.js +28 -0
  96. package/dist/optimizations-list-cli.d.ts.map +1 -1
  97. package/dist/optimizations-list-cli.js +7 -4
  98. package/dist/score-format.d.ts +2 -0
  99. package/dist/score-format.d.ts.map +1 -0
  100. package/dist/score-format.js +18 -0
  101. package/dist/workbench-repo-contract.d.ts +65 -0
  102. package/dist/workbench-repo-contract.d.ts.map +1 -0
  103. package/dist/workbench-repo-contract.js +250 -0
  104. package/dist/workspace-bootstrap-job.d.ts +45 -0
  105. package/dist/workspace-bootstrap-job.d.ts.map +1 -0
  106. package/dist/workspace-bootstrap-job.js +431 -0
  107. package/dist/workspace-bootstrap-seed.d.ts +74 -0
  108. package/dist/workspace-bootstrap-seed.d.ts.map +1 -0
  109. package/dist/workspace-bootstrap-seed.js +209 -0
  110. package/package.json +4 -3
  111. package/src/artifacts-git-runtime.ts +360 -0
  112. package/src/cloudflare-artifacts-git-remote.ts +24 -0
  113. package/src/dataset-transfer-download.ts +61 -0
  114. package/src/dataset-transfer-policy.ts +8 -0
  115. package/src/dataset-transfer-upload.ts +145 -0
  116. package/src/git-credential.ts +1 -2
  117. package/src/git-recovery-environment.ts +1 -1
  118. package/src/help.ts +44 -11
  119. package/src/hosted-boot.ts +644 -109
  120. package/src/hosted-bootstrap.ts +14 -3
  121. package/src/hosted-harness-opencode.ts +130 -28
  122. package/src/hosted-harness.ts +4 -1
  123. package/src/hosted-harvest.ts +258 -33
  124. package/src/hosted-loop-lifecycle.ts +19 -5
  125. package/src/hosted-loop.ts +340 -68
  126. package/src/hosted-question-limits.ts +6 -0
  127. package/src/hosted-question.ts +145 -0
  128. package/src/hosted-run-event-sink.ts +24 -12
  129. package/src/hosted-runtime-assets.ts +51 -11
  130. package/src/hosted-session-cli.ts +10 -24
  131. package/src/http.ts +10 -4
  132. package/src/index.ts +108 -134
  133. package/src/instruction-set-gepa-launch.ts +4 -4
  134. package/src/instruction-set-loader.ts +53 -2
  135. package/src/instruction-set-lock/index.ts +561 -0
  136. package/src/instruction-set-lock/schema.v1.json +141 -0
  137. package/src/instruction-set-lock/schema.v1.ts +155 -0
  138. package/src/instruction-set-provenance.ts +89 -0
  139. package/src/instruction-set-sync/helpers.ts +707 -0
  140. package/src/instruction-set-sync/index.ts +791 -0
  141. package/src/instruction-set-update/index.ts +488 -0
  142. package/src/instruction-set-verify/index.ts +1067 -0
  143. package/src/instruction-sets-cli.ts +226 -114
  144. package/src/model-config-fields.ts +31 -0
  145. package/src/optimizations-list-cli.ts +7 -4
  146. package/src/score-format.ts +16 -0
  147. package/src/workbench-repo-contract.ts +314 -0
  148. package/src/workspace-bootstrap-job.ts +611 -0
  149. package/src/workspace-bootstrap-seed.ts +350 -0
  150. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +46 -2
  151. package/vendor/skills/orizu/SKILL.md +4 -2
  152. package/vendor/skills/orizu/flows/promote.md +4 -0
  153. package/vendor/skills/orizu/references/assess-and-plan.md +1 -1
  154. package/vendor/skills/orizu/references/authority-map.md +1 -1
  155. package/vendor/skills/orizu/references/cli-reference.md +151 -35
  156. package/vendor/skills/orizu/references/instructions-after-prompts.md +3 -3
  157. package/vendor/skills/orizu/references/migrate-existing-gepa-setup.md +5 -6
  158. package/vendor/skills/orizu/references/migrate-hand-rolled-instruction-layout.md +252 -0
  159. package/vendor/skills/orizu/references/optimization-reports.md +5 -4
  160. package/vendor/skills/orizu/references/optimization-with-gepa.md +1 -1
  161. package/vendor/skills/orizu/references/orizu-in-your-codebase.md +195 -0
  162. package/vendor/skills/orizu/references/primer.md +4 -2
  163. package/vendor/skills/orizu/references/prompt-control-plane.md +33 -29
  164. package/vendor/skills/orizu/references/vocabulary.md +1 -1
@@ -1,6 +1,10 @@
1
1
  import { authedFetch } from './http.js'
2
+ import { INSTRUCTION_SET_COMPONENT_KEY } from './instruction-set-lock/index.js'
2
3
  import { loadInstructionSetManifest, MAX_INSTRUCTION_SET_COMPONENT_BYTES, type InstructionSetManifest } from './instruction-set-manifest.js'
3
4
  import { sanitizeHumanInlineText, sanitizeTerminalText } from './json-response.js'
5
+ import { parseSyncTarget, planSyncRequest, syncPayloadToDisk, type SyncPayload, type SyncTarget } from './instruction-set-sync/index.js'
6
+ import { applyPrune, applyUpdate, lockedProfileIdentity, makeUpdatePlan, planPrune, PruneKeepUnresolvedError, readUpdateLock } from './instruction-set-update/index.js'
7
+ import { printVerifyReport, verifyInstructionSetTree } from './instruction-set-verify/index.js'
4
8
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
5
9
  import { join } from 'node:path'
6
10
  import { createHash, randomUUID } from 'node:crypto'
@@ -31,6 +35,45 @@ function argValue(args: string[], flag: string): string | null {
31
35
  : args[index + 1]!
32
36
  }
33
37
 
38
+ function requiredOptionValue(args: string[], subcommand: string, flag: string): string | null {
39
+ const value = argValue(args, flag)
40
+ if (args.includes(flag) && value === null) {
41
+ throw new Error(`instruction_set_${subcommand}_option_value_missing:${flag}`)
42
+ }
43
+ return value
44
+ }
45
+
46
+ function waitAtPrunePlanBarrierForProcessTest(): void {
47
+ const readyPath = process.env.ORIZU_TEST_PRUNE_PLAN_READY_PATH
48
+ const releasePath = process.env.ORIZU_TEST_PRUNE_PLAN_RELEASE_PATH
49
+ if (readyPath === undefined && releasePath === undefined) return
50
+ if (!readyPath || !releasePath) throw new Error('instruction_set_prune_test_barrier_invalid')
51
+ writeFileSync(readyPath, `${process.pid}\n`, { flag: 'wx' })
52
+ const deadline = Date.now() + 10_000
53
+ while (!existsSync(releasePath)) {
54
+ if (Date.now() >= deadline) throw new Error('instruction_set_prune_test_barrier_timeout')
55
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10)
56
+ }
57
+ }
58
+
59
+ function unknownPruneOption(args: string[]): string | undefined {
60
+ const allowed = new Set(['--out', '--keep', '--yes', '--json'])
61
+ return args.find(value => value.startsWith('--') && !allowed.has(value))
62
+ }
63
+
64
+ function lifecycleOptionValues(args: string[], command: 'update' | 'prune', flag: string): string[] {
65
+ const values: string[] = []
66
+ for (let index = 0; index < args.length; index += 1) {
67
+ if (args[index] !== flag) continue
68
+ const value = args[index + 1]
69
+ if (!value || value.startsWith('--')) {
70
+ throw new Error(`instruction_set_${command}_option_missing_value:${flag}`)
71
+ }
72
+ values.push(value)
73
+ }
74
+ return values
75
+ }
76
+
34
77
  async function responsePayload(response: Response, action: string): Promise<Record<string, unknown>> {
35
78
  const payload = await response.json().catch(() => ({})) as Record<string, unknown>
36
79
  if (!response.ok) {
@@ -40,23 +83,31 @@ async function responsePayload(response: Response, action: string): Promise<Reco
40
83
  }
41
84
 
42
85
  const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/u
86
+ const WINDOWS_RESERVED_COMPONENT_KEY = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/iu
87
+
43
88
  export interface SyncComponent { body?: string; repoPath?: string; contentSha?: string; commitSha?: string }
44
89
  export interface SyncMaterial { profileVersionId: string; versionNumber: number; modelConfigIdentity: string; resolvedFrom: string; components: Record<string, SyncComponent> }
45
90
  export interface SyncProfile { modelConfigIdentity: string; resolvedFrom: string; production: SyncMaterial | null }
46
91
  export interface SyncSet {
47
- projectId?: string
48
- instructionSetId?: string
49
92
  name: string
50
- slug?: string
51
93
  description?: string | null
52
94
  shape: string[]
53
95
  default: SyncMaterial
54
96
  profiles: SyncProfile[]
55
97
  filteredTo?: string[]
56
98
  }
57
- function safeSegment(value: string) { if (!SAFE_SEGMENT.test(value) || value === '.' || value === '..' || value.startsWith('.')) throw new Error('instruction_set_path_unsafe') }
58
- function isSafeSegment(value: string) {
59
- try { safeSegment(value); return true } catch { return false }
99
+ function safeSegment(value: string) {
100
+ if (!SAFE_SEGMENT.test(value) || value === '.' || value === '..' || value.startsWith('.')) {
101
+ throw new Error('instruction_set_path_unsafe')
102
+ }
103
+ }
104
+ function safeComponentKey(value: string, platform: NodeJS.Platform) {
105
+ if (
106
+ !INSTRUCTION_SET_COMPONENT_KEY.test(value) ||
107
+ (platform === 'win32' && WINDOWS_RESERVED_COMPONENT_KEY.test(value))
108
+ ) {
109
+ throw new Error('instruction_set_path_unsafe')
110
+ }
60
111
  }
61
112
  function slug(identity: string) { return identity.replaceAll('/', '__').replace(/[^A-Za-z0-9._-]/gu, '_') }
62
113
  function stable(value: unknown): string { return `${JSON.stringify(value, null, 2)}\n` }
@@ -68,12 +119,12 @@ export interface SyncFileOps {
68
119
  rmSync?: typeof rmSync
69
120
  }
70
121
 
71
- function writeMaterial(root: string, material: SyncMaterial, shape: string[], manifestRoot: string, fileOps: SyncFileOps = { writeFileSync }) {
72
- const files: Record<string, string> = {}
73
- const syncedContentSha256: Record<string, string> = {}
74
- const pinnedComponents: Record<string, unknown> = {}
122
+ function writeMaterial(root: string, material: SyncMaterial, shape: string[], manifestRoot: string, platform: NodeJS.Platform, fileOps: SyncFileOps = { writeFileSync }) {
123
+ const files: Record<string, string> = Object.create(null)
124
+ const syncedContentSha256: Record<string, string> = Object.create(null)
125
+ const pinnedComponents: Record<string, unknown> = Object.create(null)
75
126
  for (const key of [...shape].sort()) {
76
- safeSegment(key)
127
+ safeComponentKey(key, platform)
77
128
  const component = material.components[key]
78
129
  if (typeof component?.body === 'string') {
79
130
  const relative = `${manifestRoot}/${key}.md`
@@ -94,49 +145,14 @@ function removeOwnScratch(paths: Array<string | null>) {
94
145
  for (const path of paths) if (path && existsSync(path)) rmSync(path, { recursive: true, force: true })
95
146
  }
96
147
 
97
- function existingDestinationIsSynced(
98
- destination: string,
99
- set: Pick<SyncSet, 'projectId' | 'instructionSetId' | 'name' | 'slug'>
100
- ) {
101
- if (
102
- set.slug &&
103
- set.projectId === undefined &&
104
- set.instructionSetId === undefined
105
- ) {
106
- return existingDestinationIsLegacySynced(destination, set.name, set.slug)
107
- }
108
- try {
109
- if (!statSync(destination).isDirectory()) return false
110
- const manifest = JSON.parse(readFileSync(join(destination, 'manifest.json'), 'utf8')) as {
111
- manifestVersion?: number
112
- projectId?: string
113
- instructionSetId?: string
114
- name?: string
115
- slug?: string
116
- }
117
- return manifest.manifestVersion === 1 && (set.slug
118
- ? manifest.slug === set.slug &&
119
- manifest.projectId === set.projectId &&
120
- manifest.instructionSetId === set.instructionSetId
121
- : manifest.name === set.name)
122
- } catch { return false }
123
- }
124
-
125
- function existingDestinationIsLegacySynced(
126
- destination: string,
127
- name: string,
128
- slug?: string
129
- ) {
148
+ function existingDestinationIsSynced(destination: string, set: Pick<SyncSet, 'name'>) {
130
149
  try {
131
150
  if (!statSync(destination).isDirectory()) return false
132
151
  const manifest = JSON.parse(readFileSync(join(destination, 'manifest.json'), 'utf8')) as {
133
152
  manifestVersion?: number
134
153
  name?: string
135
- slug?: string
136
154
  }
137
- return manifest.manifestVersion === 1 &&
138
- manifest.name === name &&
139
- manifest.slug === slug
155
+ return manifest.manifestVersion === 1 && manifest.name === set.name
140
156
  } catch { return false }
141
157
  }
142
158
 
@@ -156,39 +172,19 @@ function removePublishedBackups(
156
172
  if (firstError) throw new Error('instruction_set_sync_cleanup_failed')
157
173
  }
158
174
 
159
- export function syncToDisk(out: string, set: SyncSet, fileOps: SyncFileOps = { writeFileSync }) {
160
- const directoryName = set.slug || set.name
175
+ export function syncToDisk(
176
+ out: string,
177
+ set: SyncSet,
178
+ fileOps: SyncFileOps = { writeFileSync },
179
+ platform: NodeJS.Platform = process.platform
180
+ ) {
181
+ const directoryName = set.name
161
182
  safeSegment(directoryName)
162
- const hasProjectId = set.projectId !== undefined
163
- const hasInstructionSetId = set.instructionSetId !== undefined
164
- if (set.slug && hasProjectId !== hasInstructionSetId) {
165
- throw new Error('instruction_set_sync_identity_missing')
166
- }
167
183
  for (const key of set.shape) safeSegment(key)
168
- const profileSlugs = new Set<string>()
169
- for (const profile of set.profiles) {
170
- const value = slug(profile.modelConfigIdentity)
171
- safeSegment(value)
172
- if (profileSlugs.has(value)) throw new Error('instruction_set_slug_collision')
173
- profileSlugs.add(value)
174
- }
184
+ for (const profile of set.profiles) safeSegment(slug(profile.modelConfigIdentity))
175
185
  mkdirSync(out, { recursive: true })
176
186
  const destination = join(out, directoryName)
177
- const legacyDestination = set.slug && set.name !== set.slug && isSafeSegment(set.name)
178
- ? join(out, set.name)
179
- : null
180
- if (existsSync(destination)) {
181
- if (set.slug && existingDestinationIsLegacySynced(destination, set.name)) {
182
- throw new Error('instruction_set_sync_legacy_identity_required')
183
- }
184
- if (!existingDestinationIsSynced(destination, set)) {
185
- throw new Error('instruction_set_sync_destination_not_synced')
186
- }
187
- }
188
- if (legacyDestination && existsSync(legacyDestination)) {
189
- if (existingDestinationIsLegacySynced(legacyDestination, set.name)) {
190
- throw new Error('instruction_set_sync_legacy_identity_required')
191
- }
187
+ if (existsSync(destination) && !existingDestinationIsSynced(destination, set)) {
192
188
  throw new Error('instruction_set_sync_destination_not_synced')
193
189
  }
194
190
  const priorDestinations = [destination]
@@ -197,10 +193,24 @@ export function syncToDisk(out: string, set: SyncSet, fileOps: SyncFileOps = { w
197
193
  const backups: Array<{ original: string; backup: string }> = []
198
194
  mkdirSync(temp)
199
195
  try {
200
- const defaultMaterial = writeMaterial(temp, set.default, set.shape, 'default', fileOps)
196
+ const defaultMaterial = writeMaterial(
197
+ temp,
198
+ set.default,
199
+ set.shape,
200
+ 'default',
201
+ platform,
202
+ fileOps
203
+ )
201
204
  const profiles = set.profiles.map(profile => ({
202
205
  modelConfigIdentity: profile.modelConfigIdentity, slug: slug(profile.modelConfigIdentity), resolvedFrom: profile.resolvedFrom,
203
- production: profile.production ? writeMaterial(temp, profile.production, set.shape, `profiles/${slug(profile.modelConfigIdentity)}`, fileOps) : null,
206
+ production: profile.production ? writeMaterial(
207
+ temp,
208
+ profile.production,
209
+ set.shape,
210
+ `profiles/${slug(profile.modelConfigIdentity)}`,
211
+ platform,
212
+ fileOps
213
+ ) : null,
204
214
  })).sort((a, b) => a.modelConfigIdentity.localeCompare(b.modelConfigIdentity))
205
215
  const components = [
206
216
  ...Object.entries(defaultMaterial.files).map(([key, path]) => ({ key, path, syncedContentSha256: defaultMaterial.syncedContentSha256[key] })),
@@ -210,10 +220,7 @@ export function syncToDisk(out: string, set: SyncSet, fileOps: SyncFileOps = { w
210
220
  ]
211
221
  const manifest = {
212
222
  manifestVersion: 1,
213
- ...(set.projectId ? { projectId: set.projectId } : {}),
214
- ...(set.instructionSetId ? { instructionSetId: set.instructionSetId } : {}),
215
223
  name: set.name,
216
- ...(set.slug ? { slug: set.slug } : {}),
217
224
  ...(Object.hasOwn(set, 'description') ? { description: set.description ?? null } : {}),
218
225
  shape: set.shape,
219
226
  components,
@@ -247,9 +254,68 @@ export function syncToDisk(out: string, set: SyncSet, fileOps: SyncFileOps = { w
247
254
  export async function instructionSetsCommand(args: string[], io: InstructionSetsCommandIo): Promise<number> {
248
255
  const positionals = positionalArgs(args)
249
256
  const [subcommand, reference] = positionals
250
- if (subcommand === 'default' && reference === 'move') {
251
- const version = argValue(args, '--version')
252
- if (!version || !/^[0-9]+$/u.test(version) || Number(version) < 1) throw new Error('--version must be a positive integer')
257
+ const knownSubcommands = new Set([
258
+ 'archive', 'create', 'default', 'list', 'profiles', 'prune', 'push',
259
+ 'restore', 'scorers', 'shape', 'show', 'sync', 'update', 'verify',
260
+ ])
261
+ if (!subcommand || !knownSubcommands.has(subcommand)) {
262
+ throw new Error('Usage: orizu instructions list|show|sync|update|prune|verify|archive|restore')
263
+ }
264
+ if (subcommand === 'verify') {
265
+ const report = await verifyInstructionSetTree(requiredOptionValue(args, subcommand, '--out') ?? '.')
266
+ printVerifyReport(report, io.json, io.print)
267
+ return report.ok ? 0 : 1
268
+ }
269
+ if (subcommand === 'prune') {
270
+ const unknownOption = unknownPruneOption(args)
271
+ if (unknownOption) {
272
+ const code = `instruction_set_prune_option_unknown:${unknownOption}`
273
+ if (!io.json) throw new Error(code)
274
+ io.print(JSON.stringify({ removals: [], applied: false, error: code }))
275
+ return 1
276
+ }
277
+ const output = lifecycleOptionValues(args, 'prune', '--out')[0] ?? '.'
278
+ const keepSpecifiers = lifecycleOptionValues(args, 'prune', '--keep')
279
+ let plan: Awaited<ReturnType<typeof planPrune>>
280
+ try {
281
+ plan = await planPrune(output, keepSpecifiers)
282
+ } catch (error) {
283
+ if (!(io.json && error instanceof PruneKeepUnresolvedError)) throw error
284
+ io.print(JSON.stringify({ removals: [], applied: false, error: error.code, unresolved: error.unresolved }))
285
+ return 1
286
+ }
287
+ const isApproved = args.includes('--yes')
288
+ if (!isApproved) {
289
+ if (io.json) io.print(JSON.stringify({ removals: plan.removals, skipped: [], applied: false }))
290
+ else {
291
+ for (const removal of plan.removals) io.print(`Remove ${removal.key}: ${removal.reason}`)
292
+ io.print('Plan only; pass --yes to prune.')
293
+ }
294
+ return 0
295
+ }
296
+ waitAtPrunePlanBarrierForProcessTest()
297
+ try {
298
+ const result = await applyPrune(output, plan, keepSpecifiers)
299
+ if (io.json) io.print(JSON.stringify({ removals: plan.removals, skipped: result.skipped, applied: true }))
300
+ else {
301
+ for (const removal of plan.removals) io.print(`Remove ${removal.key}: ${removal.reason}`)
302
+ for (const removal of result.skipped) {
303
+ const reason = removal.reason === 'profile_directory_has_unmanaged_files'
304
+ ? `profile directory contains unmanaged files (${removal.unmanagedEntries!.join(', ')})`
305
+ : 'became referenced after planning'
306
+ io.print(`Skipped ${removal.key}: ${reason}`)
307
+ }
308
+ }
309
+ return 0
310
+ } catch (error) {
311
+ if (!io.json) throw error
312
+ const code = error instanceof Error ? error.message : String(error)
313
+ io.print(JSON.stringify({ removals: plan.removals, applied: false, error: code }))
314
+ return 1
315
+ }
316
+ }
317
+ if (subcommand === 'default' && reference === 'move' && args.some(arg => arg === '--version' || arg.startsWith('--version='))) {
318
+ throw new Error('--version is not valid for default move; use instructions profiles promote')
253
319
  }
254
320
  let writeManifest: InstructionSetManifest | undefined
255
321
  if (subcommand === 'create' || subcommand === 'push') {
@@ -258,7 +324,48 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
258
324
  : 'Usage: orizu instructions push <manifest> --project <team/project> [--set <slug-or-exact-name>] [--runner-version <id>] [--json]')
259
325
  writeManifest = loadInstructionSetManifest(reference)
260
326
  }
261
- const project = await io.resolveProjectSlug(argValue(args, '--project'))
327
+ let syncTarget: SyncTarget | undefined
328
+ if (subcommand === 'sync') {
329
+ for (const option of ['--project', '--out', '--version', '--target']) {
330
+ if (args.filter(value => value === option).length > 1) {
331
+ throw new Error(`instruction_set_sync_option_duplicate:${option}`)
332
+ }
333
+ }
334
+ const suppliedTarget = argValue(args, '--target')
335
+ syncTarget = parseSyncTarget(args.includes('--target') ? suppliedTarget ?? '' : undefined)
336
+ }
337
+ const projectOption = subcommand === 'update'
338
+ ? lifecycleOptionValues(args, 'update', '--project')[0] ?? null
339
+ : requiredOptionValue(args, subcommand, '--project')
340
+ const project = await io.resolveProjectSlug(projectOption)
341
+ if (subcommand === 'update') {
342
+ const output = lifecycleOptionValues(args, 'update', '--out')[0] ?? '.'
343
+ const lock = readUpdateLock(output, project)
344
+ const payloads: SyncPayload[] = []
345
+ for (const [setSlug, set] of Object.entries(lock.instructionSets)) {
346
+ const barePath = `/api/cli/instruction-sets/${encodeURIComponent(setSlug)}/sync?project=${encodeURIComponent(project)}`
347
+ payloads.push(await responsePayload(await authedFetch(barePath, { method: 'GET' }), 'Instruction sets update') as unknown as SyncPayload)
348
+ for (const profileSlugValue of Object.keys(set.profiles)) {
349
+ const identity = lockedProfileIdentity(output, setSlug, profileSlugValue)
350
+ const query = new URLSearchParams({ project, profile: identity })
351
+ const path = `/api/cli/instruction-sets/${encodeURIComponent(setSlug)}/sync?${query.toString()}`
352
+ payloads.push(await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets update') as unknown as SyncPayload)
353
+ }
354
+ }
355
+ const plan = makeUpdatePlan(project, lock, payloads)
356
+ const isApproved = args.includes('--yes')
357
+ const result = isApproved
358
+ ? await applyUpdate(output, project, plan, args.includes('--no-sync'))
359
+ : { absent: [], warnings: [] }
360
+ if (io.json) io.print(JSON.stringify({ plan: plan.lines, applied: isApproved, absent: result.absent, warnings: result.warnings }))
361
+ else {
362
+ for (const line of plan.lines) io.print(line)
363
+ for (const specifier of result.absent) io.print(`Referenced but absent: ${specifier}`)
364
+ for (const warning of result.warnings) io.print(warning)
365
+ if (!isApproved) io.print('Plan only; pass --yes to update.')
366
+ }
367
+ return 0
368
+ }
262
369
  if (subcommand === 'scorers') {
263
370
  const action = reference
264
371
  const set = positionals[2]
@@ -298,22 +405,20 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
298
405
  return 0
299
406
  }
300
407
  if (subcommand === 'default') {
301
- const operation = reference; const set = positionals[2]; const identity = argValue(args, '--model-config'); const version = argValue(args, '--version')
302
- if ((operation !== 'show' && operation !== 'move') || !set) throw new Error('Usage: orizu instructions default show|move <set> --project <team/project> [--model-config <identity> --version <n>] [--json]')
303
- if (operation === 'move' && (!identity || !version || !/^[0-9]+$/u.test(version) || Number(version) < 1)) throw new Error('--version must be a positive integer')
408
+ const operation = reference; const set = positionals[2]; const identity = argValue(args, '--model-config')
409
+ if ((operation !== 'show' && operation !== 'move') || !set) throw new Error('Usage: orizu instructions default show|move <set> --project <team/project> [--model-config <identity>] [--json]')
410
+ if (operation === 'move' && !identity) throw new Error('--model-config is required')
304
411
  const path = `/api/cli/instruction-sets/${encodeURIComponent(set)}/default?project=${encodeURIComponent(project)}`
305
412
  if (operation === 'show') {
306
413
  const payload = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets default show')
307
- if (io.json) io.print(JSON.stringify(payload)); else io.print(`Default: v${(payload.default as { versionNumber?: number } | undefined)?.versionNumber ?? '?'}`)
414
+ const current = payload.default as { modelConfigIdentity?: string; production?: { versionNumber?: number } | null } | null
415
+ if (io.json) io.print(JSON.stringify(payload))
416
+ else if (!current?.modelConfigIdentity) io.print('Default: unset')
417
+ else io.print(`Default: ${sanitizeTerminalText(current.modelConfigIdentity)} (${current.production ? `production v${current.production.versionNumber ?? '?'}` : 'not promoted'})`)
308
418
  return 0
309
419
  }
310
- if (!io.json) {
311
- const impact = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets default show')
312
- const resolvesToDefault = Array.isArray(impact.resolvesToDefault) ? impact.resolvesToDefault.filter((item): item is string => typeof item === 'string') : []
313
- io.print(`These model configs resolve to the default: ${resolvesToDefault.join(', ')}`)
314
- }
315
- const payload = await responsePayload(await authedFetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modelConfigIdentity: identity, versionNumber: Number(version) }) }), 'Instruction sets default move')
316
- if (io.json) io.print(JSON.stringify(payload)); else io.print(`Default moved to ${identity} v${version}`)
420
+ const payload = await responsePayload(await authedFetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modelConfigIdentity: identity }) }), 'Instruction sets default move')
421
+ if (io.json) io.print(JSON.stringify(payload)); else io.print(`Default moved to ${sanitizeTerminalText(identity!)}`)
317
422
  return 0
318
423
  }
319
424
  if (subcommand === 'shape') {
@@ -337,10 +442,9 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
337
442
  if (identities.length) io.print(`${set} does not resolve for these model configs until the pointers move: ${identities.join(', ')}`)
338
443
  for (const pointer of affected) if (pointer && typeof pointer === 'object') {
339
444
  const item = pointer as { modelConfigIdentity?: unknown; pointer?: unknown; stalePointerVersionNumber?: unknown; headVersionNumber?: unknown; branchedFromVersionNumber?: unknown }
340
- if (typeof item.modelConfigIdentity === 'string' && (item.pointer === 'production' || item.pointer === 'default') && typeof item.headVersionNumber === 'number') {
341
- const command = item.pointer === 'production' ? 'profiles promote' : 'default move'
342
- const operatorAction = item.pointer === 'production' ? 'promote' : 'default move'
343
- if (typeof item.stalePointerVersionNumber === 'number' && typeof item.branchedFromVersionNumber === 'number' && item.branchedFromVersionNumber !== item.stalePointerVersionNumber) io.print(`${operatorAction} would move ${item.pointer} from v${item.stalePointerVersionNumber}'s text to v${item.headVersionNumber} = v${item.branchedFromVersionNumber}'s text + ${key}`)
445
+ if (typeof item.modelConfigIdentity === 'string' && item.pointer === 'production' && typeof item.headVersionNumber === 'number') {
446
+ const command = 'profiles promote'
447
+ if (typeof item.stalePointerVersionNumber === 'number' && typeof item.branchedFromVersionNumber === 'number' && item.branchedFromVersionNumber !== item.stalePointerVersionNumber) io.print(`promote would move ${item.pointer} from v${item.stalePointerVersionNumber}'s text to v${item.headVersionNumber} = v${item.branchedFromVersionNumber}'s text + ${key}`)
344
448
  io.print(`Follow up: instructions ${command} ${set} --project ${project} --model-config ${item.modelConfigIdentity} --version ${item.headVersionNumber}`)
345
449
  }
346
450
  }
@@ -413,19 +517,27 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
413
517
  return 0
414
518
  }
415
519
  const status = argValue(args, '--status') || 'active'
416
- if (subcommand !== 'list' && subcommand !== 'show' && subcommand !== 'sync') throw new Error('Usage: orizu instructions list|show|sync|archive|restore')
417
520
  if (subcommand === 'sync') {
418
- const output = argValue(args, '--out')
419
- if (!reference || !output) throw new Error('Usage: orizu instructions sync <set> --out <dir> --project <team/project> [--model-config <identity>] [--json]')
420
- const modelConfig = argValue(args, '--model-config')
421
- const path = `/api/cli/instruction-sets/${encodeURIComponent(reference)}/sync?project=${encodeURIComponent(project)}${modelConfig ? `&modelConfig=${encodeURIComponent(modelConfig)}` : ''}`
422
- const payload = await responsePayload(await authedFetch(path, { method: 'GET' }), 'Instruction sets sync')
423
- const set = payload.instructionSet as SyncSet
424
- if (!set || (set.slug !== reference && set.name !== reference) || (modelConfig && (!Array.isArray(set.filteredTo) || set.filteredTo.length !== 1 || set.filteredTo[0] !== modelConfig))) {
425
- throw new Error('instruction_set_sync_response_mismatch')
521
+ if (!reference) throw new Error('Usage: orizu instructions sync <specifier> [--version <n>] [--out <app-root>] [--target <ts>] [--force-helpers] --project <team/project> [--json]')
522
+ if (args.includes('--model-config')) {
523
+ throw new Error('instruction_set_sync_option_retired:--model-config; use <set>/<profile> (see docs/cli.md#specifiers)')
524
+ }
525
+ const allowedOptions = new Set(['--project', '--out', '--version', '--target', '--json', '--force-helpers'])
526
+ const unknownOption = args.find(value => value.startsWith('--') && !allowedOptions.has(value))
527
+ if (unknownOption) throw new Error(`instruction_set_sync_option_unknown:${unknownOption}`)
528
+ const output = requiredOptionValue(args, subcommand, '--out') ?? '.'
529
+ const plan = planSyncRequest(reference, requiredOptionValue(args, subcommand, '--version'), output, project)
530
+ const payload = await responsePayload(await authedFetch(plan.path, { method: 'GET' }), 'Instruction sets sync') as unknown as SyncPayload
531
+ const result = syncPayloadToDisk(output, project, plan, payload, {
532
+ forceHelpers: args.includes('--force-helpers'),
533
+ target: syncTarget!,
534
+ })
535
+ if (io.json) io.print(JSON.stringify({ ...payload, warnings: result.warnings }))
536
+ else {
537
+ io.print(`Synced ${reference} to ${result.destination}`)
538
+ for (const warning of result.warnings) io.print(`Warning: ${warning}`)
539
+ if (plan.usedRecordedPointer) io.print('Using recorded Pointer values; run orizu instructions update to re-resolve them.')
426
540
  }
427
- const destination = syncToDisk(output, set)
428
- if (io.json) io.print(JSON.stringify(payload)); else io.print(`Synced ${set.name} to ${destination}`)
429
541
  return 0
430
542
  }
431
543
  if (subcommand === 'show' && (!reference || reference.startsWith('--'))) throw new Error('Instruction set name is required')
@@ -449,10 +561,10 @@ export async function instructionSetsCommand(args: string[], io: InstructionSets
449
561
  }
450
562
  return 0
451
563
  }
452
- const set = payload.instructionSet as { name?: string; description?: string | null; shape?: string[]; status?: string; default?: { versionNumber?: number } | null; profiles?: Array<{ modelConfigIdentity?: string | null; production?: { versionNumber?: number } | null; latestVersionNumber?: number | null }> } | undefined
564
+ const set = payload.instructionSet as { name?: string; description?: string | null; shape?: string[]; status?: string; default?: { modelConfigIdentity?: string } | null; profiles?: Array<{ modelConfigIdentity?: string | null; production?: { versionNumber?: number } | null; latestVersionNumber?: number | null }> } | undefined
453
565
  io.print(`${set?.name || reference}${set?.status === 'archived' ? ' [archived]' : ''}: ${set?.shape?.join(', ') || ''}`)
454
566
  if (set?.description) io.print(`Description: ${sanitizeHumanInlineText(sanitizeTerminalText, set.description)}`)
455
- if (set?.default) io.print(`Default: v${set.default.versionNumber ?? '?'}`)
567
+ if (set?.default?.modelConfigIdentity) io.print(`Default: ${sanitizeTerminalText(set.default.modelConfigIdentity)}`)
456
568
  for (const profile of set?.profiles || []) {
457
569
  io.print(`${profile.modelConfigIdentity || 'unspecified'}: production ${profile.production ? `v${profile.production.versionNumber}` : '—'}, latest ${profile.latestVersionNumber ?? '—'}`)
458
570
  }
@@ -0,0 +1,31 @@
1
+ export const MODEL_CONFIG_FIELDS = [
2
+ // The pilot's frozen settings carry the provider model name under `model`.
3
+ { field: 'MODEL', settingsKeys: ['model'], kind: 'string' },
4
+ // `protocol` names the transport/API protocol selected for a customer model call.
5
+ { field: 'PROTOCOL', settingsKeys: ['protocol'], kind: 'string' },
6
+ // The pilot uses `reasoning_effort`; hosted settings accept OpenAI `reasoning.effort` and Anthropic `output_config.effort`.
7
+ { field: 'THINKING_LEVEL', settingsKeys: ['reasoning_effort', 'reasoning.effort', 'output_config.effort'], kind: 'string' },
8
+ // Responses uses `max_output_tokens`; Messages uses `max_tokens` for the output limit.
9
+ { field: 'MAX_OUTPUT_TOKENS', settingsKeys: ['max_output_tokens', 'max_tokens'], kind: 'number' },
10
+ // `temperature` feeds the temperature call option for both supported providers.
11
+ { field: 'TEMPERATURE', settingsKeys: ['temperature'], kind: 'number' },
12
+ // `top_p` feeds the top-p call option accepted by both supported providers.
13
+ { field: 'TOP_P', settingsKeys: ['top_p'], kind: 'number' },
14
+ // `strict_json_schema` controls strict schema enforcement in a structured-output call.
15
+ { field: 'STRICT_JSON_SCHEMA', settingsKeys: ['strict_json_schema'], kind: 'boolean' },
16
+ ] as const
17
+
18
+ export type ModelConfigField = typeof MODEL_CONFIG_FIELDS[number]['field']
19
+
20
+ // Dotted settings keys resolve exactly one object level; they are not a general path language.
21
+ export const PROVIDER_REQUEST_FIELDS: Record<ModelConfigField, { openai: string | null; anthropic: string | null }> = {
22
+ MODEL: { openai: 'model', anthropic: 'model' },
23
+ // Orizu consumes protocol selection; it is not a provider request field.
24
+ PROTOCOL: { openai: null, anthropic: null },
25
+ THINKING_LEVEL: { openai: 'reasoning.effort', anthropic: 'output_config.effort' },
26
+ MAX_OUTPUT_TOKENS: { openai: 'max_output_tokens', anthropic: 'max_tokens' },
27
+ TEMPERATURE: { openai: 'temperature', anthropic: 'temperature' },
28
+ TOP_P: { openai: 'top_p', anthropic: 'top_p' },
29
+ // Orizu consumes strict schema selection; it is not a provider request field.
30
+ STRICT_JSON_SCHEMA: { openai: null, anthropic: null },
31
+ }
@@ -28,6 +28,7 @@ interface OptimizationRunSummary {
28
28
  archiveStatus?: string
29
29
  archivedAt?: string | null
30
30
  optimizerVersionId: string | null
31
+ profileVersionId: string | null
31
32
  datasetVersionId: string | null
32
33
  bestScore: number | null
33
34
  resultPromptVersionId: string | null
@@ -80,11 +81,13 @@ export async function listOptimizationRunsCommand(args: string[], io: Optimizati
80
81
  if (!['active', 'archived', 'all'].includes(status)) {
81
82
  throw new Error(
82
83
  'Usage: orizu optimizations list [--project <team/project>] ' +
83
- '[--status active|archived|all] [--json]'
84
+ '[--status active|archived|all] [--profile-version <id>] [--json]'
84
85
  )
85
86
  }
86
87
  const params = new URLSearchParams({ project })
87
88
  if (status !== 'active') params.set('status', status)
89
+ const profileVersionId = argValue(args, '--profile-version')
90
+ if (profileVersionId) params.set('profileVersionId', profileVersionId)
88
91
  const response = await fetcher(`/api/cli/optimization-runs?${params.toString()}`)
89
92
  if (!response.ok) {
90
93
  throw new Error(
@@ -121,13 +124,13 @@ export async function listOptimizationRunsCommand(args: string[], io: Optimizati
121
124
  'ARCHIVE'.length,
122
125
  ...rows.map(row => String(row.archiveStatus || 'active').length)
123
126
  )
124
- io.print(`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'ARCHIVE'.padEnd(archiveWidth)} BEST PROMOTED_VERSION`)
125
- io.print(`${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(archiveWidth)} ${'-'.repeat(5)} ${'-'.repeat('PROMOTED_VERSION'.length)}`)
127
+ io.print(`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'ARCHIVE'.padEnd(archiveWidth)} BEST PROFILE_VERSION PROMOTED_VERSION`)
128
+ io.print(`${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(archiveWidth)} ${'-'.repeat(5)} ${'-'.repeat('PROFILE_VERSION'.length)} ${'-'.repeat('PROMOTED_VERSION'.length)}`)
126
129
  for (const row of rows) {
127
130
  const best = row.bestScore === null || row.bestScore === undefined ? '-' : String(row.bestScore)
128
131
  const promoted = row.resultPromptVersionId || '-'
129
132
  const archiveStatus = row.archiveStatus || 'active'
130
- io.print(`${String(row.id).padEnd(idWidth)} ${String(row.status).padEnd(statusWidth)} ${archiveStatus.padEnd(archiveWidth)} ${best.padEnd(5)} ${promoted}`)
133
+ io.print(`${String(row.id).padEnd(idWidth)} ${String(row.status).padEnd(statusWidth)} ${archiveStatus.padEnd(archiveWidth)} ${best.padEnd(5)} ${row.profileVersionId || '-'} ${promoted}`)
131
134
  }
132
135
  // ALI-1175: label the claim — nothing here is server-attested.
133
136
  io.print(BEST_SCORE_PROVENANCE_NOTICE)
@@ -0,0 +1,16 @@
1
+ const SCORE_FORMATS = new Set(['percent', 'number', 'integer', 'currency'])
2
+
3
+ export function formatScoreForCli(score: unknown, scoreFormat: unknown): string {
4
+ if (typeof scoreFormat !== 'string' || !SCORE_FORMATS.has(scoreFormat)) {
5
+ return String(score ?? '—')
6
+ }
7
+ if (score === null || score === undefined || (typeof score === 'number' && Number.isNaN(score))) {
8
+ return 'Not scored'
9
+ }
10
+ if (typeof score !== 'number') return String(score)
11
+
12
+ if (scoreFormat === 'number') return score.toFixed(3).replace(/\.?0+$/u, '')
13
+ if (scoreFormat === 'integer') return Math.round(score).toLocaleString()
14
+ if (scoreFormat === 'currency') return `$${score.toFixed(2)}`
15
+ return `${(score * 100).toFixed(1)}%`
16
+ }