@clawplays/ospec-cli 0.1.1

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 (191) hide show
  1. package/.ospec/templates/hooks/post-merge +8 -0
  2. package/.ospec/templates/hooks/pre-commit +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +549 -0
  5. package/README.zh-CN.md +549 -0
  6. package/assets/for-ai/en-US/ai-guide.md +98 -0
  7. package/assets/for-ai/en-US/execution-protocol.md +64 -0
  8. package/assets/for-ai/zh-CN/ai-guide.md +102 -0
  9. package/assets/for-ai/zh-CN/execution-protocol.md +68 -0
  10. package/assets/git-hooks/post-merge +12 -0
  11. package/assets/git-hooks/pre-commit +12 -0
  12. package/assets/global-skills/claude/ospec-change/SKILL.md +116 -0
  13. package/assets/global-skills/codex/ospec-change/SKILL.md +117 -0
  14. package/assets/global-skills/codex/ospec-change/agents/openai.yaml +7 -0
  15. package/assets/global-skills/codex/ospec-change/skill.yaml +19 -0
  16. package/assets/project-conventions/en-US/development-guide.md +32 -0
  17. package/assets/project-conventions/en-US/naming-conventions.md +51 -0
  18. package/assets/project-conventions/en-US/skill-conventions.md +40 -0
  19. package/assets/project-conventions/en-US/workflow-conventions.md +70 -0
  20. package/assets/project-conventions/zh-CN/development-guide.md +32 -0
  21. package/assets/project-conventions/zh-CN/naming-conventions.md +51 -0
  22. package/assets/project-conventions/zh-CN/skill-conventions.md +40 -0
  23. package/assets/project-conventions/zh-CN/workflow-conventions.md +74 -0
  24. package/dist/adapters/codex-stitch-adapter.js +420 -0
  25. package/dist/adapters/gemini-stitch-adapter.js +408 -0
  26. package/dist/adapters/playwright-checkpoint-adapter.js +2260 -0
  27. package/dist/advanced/BatchOperations.d.ts +36 -0
  28. package/dist/advanced/BatchOperations.js +159 -0
  29. package/dist/advanced/CachingLayer.d.ts +66 -0
  30. package/dist/advanced/CachingLayer.js +136 -0
  31. package/dist/advanced/FeatureUpdater.d.ts +46 -0
  32. package/dist/advanced/FeatureUpdater.js +151 -0
  33. package/dist/advanced/PerformanceMonitor.d.ts +52 -0
  34. package/dist/advanced/PerformanceMonitor.js +129 -0
  35. package/dist/advanced/StatePersistence.d.ts +61 -0
  36. package/dist/advanced/StatePersistence.js +168 -0
  37. package/dist/advanced/index.d.ts +14 -0
  38. package/dist/advanced/index.js +22 -0
  39. package/dist/cli/commands/config.d.ts +5 -0
  40. package/dist/cli/commands/config.js +6 -0
  41. package/dist/cli/commands/feature.d.ts +5 -0
  42. package/dist/cli/commands/feature.js +6 -0
  43. package/dist/cli/commands/index.d.ts +5 -0
  44. package/dist/cli/commands/index.js +6 -0
  45. package/dist/cli/commands/project.d.ts +5 -0
  46. package/dist/cli/commands/project.js +6 -0
  47. package/dist/cli/commands/validate.d.ts +5 -0
  48. package/dist/cli/commands/validate.js +6 -0
  49. package/dist/cli/index.d.ts +5 -0
  50. package/dist/cli/index.js +6 -0
  51. package/dist/cli.d.ts +3 -0
  52. package/dist/cli.js +1007 -0
  53. package/dist/commands/ArchiveCommand.d.ts +14 -0
  54. package/dist/commands/ArchiveCommand.js +241 -0
  55. package/dist/commands/BaseCommand.d.ts +33 -0
  56. package/dist/commands/BaseCommand.js +46 -0
  57. package/dist/commands/BatchCommand.d.ts +5 -0
  58. package/dist/commands/BatchCommand.js +42 -0
  59. package/dist/commands/ChangesCommand.d.ts +3 -0
  60. package/dist/commands/ChangesCommand.js +71 -0
  61. package/dist/commands/DocsCommand.d.ts +5 -0
  62. package/dist/commands/DocsCommand.js +118 -0
  63. package/dist/commands/FinalizeCommand.d.ts +3 -0
  64. package/dist/commands/FinalizeCommand.js +24 -0
  65. package/dist/commands/IndexCommand.d.ts +5 -0
  66. package/dist/commands/IndexCommand.js +57 -0
  67. package/dist/commands/InitCommand.d.ts +5 -0
  68. package/dist/commands/InitCommand.js +65 -0
  69. package/dist/commands/NewCommand.d.ts +11 -0
  70. package/dist/commands/NewCommand.js +262 -0
  71. package/dist/commands/PluginsCommand.d.ts +58 -0
  72. package/dist/commands/PluginsCommand.js +2491 -0
  73. package/dist/commands/ProgressCommand.d.ts +5 -0
  74. package/dist/commands/ProgressCommand.js +103 -0
  75. package/dist/commands/QueueCommand.d.ts +10 -0
  76. package/dist/commands/QueueCommand.js +147 -0
  77. package/dist/commands/RunCommand.d.ts +13 -0
  78. package/dist/commands/RunCommand.js +200 -0
  79. package/dist/commands/SkillCommand.d.ts +31 -0
  80. package/dist/commands/SkillCommand.js +1216 -0
  81. package/dist/commands/SkillsCommand.d.ts +5 -0
  82. package/dist/commands/SkillsCommand.js +68 -0
  83. package/dist/commands/StatusCommand.d.ts +6 -0
  84. package/dist/commands/StatusCommand.js +140 -0
  85. package/dist/commands/UpdateCommand.d.ts +8 -0
  86. package/dist/commands/UpdateCommand.js +251 -0
  87. package/dist/commands/VerifyCommand.d.ts +5 -0
  88. package/dist/commands/VerifyCommand.js +278 -0
  89. package/dist/commands/WorkflowCommand.d.ts +12 -0
  90. package/dist/commands/WorkflowCommand.js +150 -0
  91. package/dist/commands/index.d.ts +43 -0
  92. package/dist/commands/index.js +85 -0
  93. package/dist/core/constants.d.ts +41 -0
  94. package/dist/core/constants.js +73 -0
  95. package/dist/core/errors.d.ts +36 -0
  96. package/dist/core/errors.js +72 -0
  97. package/dist/core/index.d.ts +7 -0
  98. package/dist/core/index.js +23 -0
  99. package/dist/core/types.d.ts +369 -0
  100. package/dist/core/types.js +3 -0
  101. package/dist/index.d.ts +11 -0
  102. package/dist/index.js +27 -0
  103. package/dist/presets/ProjectPresets.d.ts +41 -0
  104. package/dist/presets/ProjectPresets.js +190 -0
  105. package/dist/scaffolds/ProjectScaffoldPresets.d.ts +20 -0
  106. package/dist/scaffolds/ProjectScaffoldPresets.js +151 -0
  107. package/dist/services/ConfigManager.d.ts +14 -0
  108. package/dist/services/ConfigManager.js +386 -0
  109. package/dist/services/FeatureManager.d.ts +5 -0
  110. package/dist/services/FeatureManager.js +6 -0
  111. package/dist/services/FileService.d.ts +21 -0
  112. package/dist/services/FileService.js +152 -0
  113. package/dist/services/IndexBuilder.d.ts +12 -0
  114. package/dist/services/IndexBuilder.js +130 -0
  115. package/dist/services/Logger.d.ts +20 -0
  116. package/dist/services/Logger.js +48 -0
  117. package/dist/services/ProjectAssetRegistry.d.ts +12 -0
  118. package/dist/services/ProjectAssetRegistry.js +96 -0
  119. package/dist/services/ProjectAssetService.d.ts +49 -0
  120. package/dist/services/ProjectAssetService.js +223 -0
  121. package/dist/services/ProjectScaffoldCommandService.d.ts +73 -0
  122. package/dist/services/ProjectScaffoldCommandService.js +159 -0
  123. package/dist/services/ProjectScaffoldService.d.ts +44 -0
  124. package/dist/services/ProjectScaffoldService.js +507 -0
  125. package/dist/services/ProjectService.d.ts +209 -0
  126. package/dist/services/ProjectService.js +13239 -0
  127. package/dist/services/QueueService.d.ts +17 -0
  128. package/dist/services/QueueService.js +142 -0
  129. package/dist/services/RunService.d.ts +40 -0
  130. package/dist/services/RunService.js +420 -0
  131. package/dist/services/SkillParser.d.ts +30 -0
  132. package/dist/services/SkillParser.js +88 -0
  133. package/dist/services/StateManager.d.ts +16 -0
  134. package/dist/services/StateManager.js +127 -0
  135. package/dist/services/TemplateEngine.d.ts +43 -0
  136. package/dist/services/TemplateEngine.js +119 -0
  137. package/dist/services/TemplateGenerator.d.ts +40 -0
  138. package/dist/services/TemplateGenerator.js +273 -0
  139. package/dist/services/ValidationService.d.ts +19 -0
  140. package/dist/services/ValidationService.js +44 -0
  141. package/dist/services/Validator.d.ts +5 -0
  142. package/dist/services/Validator.js +6 -0
  143. package/dist/services/index.d.ts +52 -0
  144. package/dist/services/index.js +91 -0
  145. package/dist/services/templates/ExecutionTemplateBuilder.d.ts +12 -0
  146. package/dist/services/templates/ExecutionTemplateBuilder.js +300 -0
  147. package/dist/services/templates/ProjectTemplateBuilder.d.ts +38 -0
  148. package/dist/services/templates/ProjectTemplateBuilder.js +1897 -0
  149. package/dist/services/templates/TemplateBuilderBase.d.ts +19 -0
  150. package/dist/services/templates/TemplateBuilderBase.js +60 -0
  151. package/dist/services/templates/TemplateInputFactory.d.ts +16 -0
  152. package/dist/services/templates/TemplateInputFactory.js +298 -0
  153. package/dist/services/templates/templateTypes.d.ts +90 -0
  154. package/dist/services/templates/templateTypes.js +3 -0
  155. package/dist/tools/build-index.js +632 -0
  156. package/dist/utils/DateUtils.d.ts +18 -0
  157. package/dist/utils/DateUtils.js +40 -0
  158. package/dist/utils/PathUtils.d.ts +9 -0
  159. package/dist/utils/PathUtils.js +66 -0
  160. package/dist/utils/StringUtils.d.ts +26 -0
  161. package/dist/utils/StringUtils.js +47 -0
  162. package/dist/utils/helpers.d.ts +5 -0
  163. package/dist/utils/helpers.js +6 -0
  164. package/dist/utils/index.d.ts +7 -0
  165. package/dist/utils/index.js +23 -0
  166. package/dist/utils/logger.d.ts +5 -0
  167. package/dist/utils/logger.js +6 -0
  168. package/dist/utils/path.d.ts +5 -0
  169. package/dist/utils/path.js +6 -0
  170. package/dist/utils/subcommandHelp.d.ts +11 -0
  171. package/dist/utils/subcommandHelp.js +119 -0
  172. package/dist/workflow/ArchiveGate.d.ts +30 -0
  173. package/dist/workflow/ArchiveGate.js +93 -0
  174. package/dist/workflow/ConfigurableWorkflow.d.ts +89 -0
  175. package/dist/workflow/ConfigurableWorkflow.js +186 -0
  176. package/dist/workflow/HookSystem.d.ts +38 -0
  177. package/dist/workflow/HookSystem.js +66 -0
  178. package/dist/workflow/IndexRegenerator.d.ts +49 -0
  179. package/dist/workflow/IndexRegenerator.js +147 -0
  180. package/dist/workflow/PluginWorkflowComposer.d.ts +138 -0
  181. package/dist/workflow/PluginWorkflowComposer.js +239 -0
  182. package/dist/workflow/SkillUpdateEngine.d.ts +26 -0
  183. package/dist/workflow/SkillUpdateEngine.js +113 -0
  184. package/dist/workflow/VerificationSystem.d.ts +24 -0
  185. package/dist/workflow/VerificationSystem.js +116 -0
  186. package/dist/workflow/WorkflowEngine.d.ts +15 -0
  187. package/dist/workflow/WorkflowEngine.js +57 -0
  188. package/dist/workflow/index.d.ts +19 -0
  189. package/dist/workflow/index.js +32 -0
  190. package/package.json +78 -0
  191. package/scripts/postinstall.js +43 -0
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const { spawnSync } = require("child_process");
7
+ function fail(message, details, code = 'generic') {
8
+ const suffix = details ? ` ${details}` : '';
9
+ process.stderr.write(`Codex Stitch adapter failed [${code}]: ${message}${suffix}\n`);
10
+ process.exit(1);
11
+ }
12
+ function parseArgs(argv) {
13
+ const result = {};
14
+ for (let index = 0; index < argv.length; index += 1) {
15
+ const current = argv[index];
16
+ if (!current.startsWith('--')) {
17
+ continue;
18
+ }
19
+ const next = argv[index + 1];
20
+ if (!next || next.startsWith('--')) {
21
+ result[current.slice(2)] = 'true';
22
+ continue;
23
+ }
24
+ result[current.slice(2)] = next;
25
+ index += 1;
26
+ }
27
+ return result;
28
+ }
29
+ function readFileIfExists(filePath) {
30
+ if (!filePath || !fs.existsSync(filePath)) {
31
+ return '';
32
+ }
33
+ return fs.readFileSync(filePath, 'utf8');
34
+ }
35
+ function readJsonIfExists(filePath) {
36
+ if (!filePath || !fs.existsSync(filePath)) {
37
+ return null;
38
+ }
39
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
40
+ }
41
+ function truncate(text, maxLength) {
42
+ const normalized = String(text || '').trim();
43
+ if (normalized.length <= maxLength) {
44
+ return normalized;
45
+ }
46
+ return `${normalized.slice(0, maxLength)}\n...[truncated]`;
47
+ }
48
+ function parseBooleanFlag(value, defaultValue = false) {
49
+ if (typeof value !== 'string' || value.trim().length === 0) {
50
+ return defaultValue;
51
+ }
52
+ const normalized = value.trim().toLowerCase();
53
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) {
54
+ return true;
55
+ }
56
+ if (['0', 'false', 'no', 'off'].includes(normalized)) {
57
+ return false;
58
+ }
59
+ return defaultValue;
60
+ }
61
+ function inspectCodexStitchConfig(settingsText) {
62
+ const normalized = String(settingsText || '');
63
+ const stitchMatch = normalized.match(/\[mcp_servers\.stitch\]([\s\S]*?)(?=\r?\n\[|$)/i);
64
+ const stitchBlock = stitchMatch ? stitchMatch[1] : '';
65
+ const httpHeadersMatch = normalized.match(/\[mcp_servers\.stitch\.http_headers\]([\s\S]*?)(?=\r?\n\[|$)/i);
66
+ const httpHeadersBlock = httpHeadersMatch ? httpHeadersMatch[1] : '';
67
+ return {
68
+ stitchConfigured: Boolean(stitchMatch),
69
+ stitchTransportHttp: /(^|\r?\n)\s*type\s*=\s*["']http["']/i.test(stitchBlock),
70
+ stitchUrlConfigured: /(^|\r?\n)\s*url\s*=\s*["']https:\/\/stitch\.googleapis\.com\/mcp["']/i.test(stitchBlock),
71
+ stitchAuthConfigured: /(^|\r?\n)\s*headers\s*=\s*\{[\s\S]*?\bX-Goog-Api-Key\b\s*=\s*["'][^"']+["'][\s\S]*?\}/i.test(stitchBlock)
72
+ || /\bX-Goog-Api-Key\b\s*=\s*["'][^"']+["']/i.test(httpHeadersBlock),
73
+ };
74
+ }
75
+ function inspectCodexSettings() {
76
+ const settingsPath = path.join(os.homedir(), '.codex', 'config.toml');
77
+ if (!fs.existsSync(settingsPath)) {
78
+ return {
79
+ settingsPath,
80
+ exists: false,
81
+ stitchConfigured: false,
82
+ stitchTransportHttp: false,
83
+ stitchUrlConfigured: false,
84
+ stitchAuthConfigured: false,
85
+ };
86
+ }
87
+ try {
88
+ const settings = fs.readFileSync(settingsPath, 'utf8');
89
+ const stitchConfig = inspectCodexStitchConfig(settings);
90
+ return {
91
+ settingsPath,
92
+ exists: true,
93
+ stitchConfigured: stitchConfig.stitchConfigured,
94
+ stitchTransportHttp: stitchConfig.stitchTransportHttp,
95
+ stitchUrlConfigured: stitchConfig.stitchUrlConfigured,
96
+ stitchAuthConfigured: stitchConfig.stitchAuthConfigured,
97
+ };
98
+ }
99
+ catch {
100
+ return {
101
+ settingsPath,
102
+ exists: true,
103
+ stitchConfigured: false,
104
+ stitchTransportHttp: false,
105
+ stitchUrlConfigured: false,
106
+ stitchAuthConfigured: false,
107
+ };
108
+ }
109
+ }
110
+ function resolveCodexCommand(projectPath) {
111
+ const locator = process.platform === 'win32' ? 'where.exe' : 'which';
112
+ const lookup = spawnSync(locator, ['codex'], {
113
+ cwd: projectPath,
114
+ encoding: 'utf8',
115
+ shell: false,
116
+ });
117
+ if (lookup.error || lookup.status !== 0) {
118
+ return '';
119
+ }
120
+ const lines = String(lookup.stdout || '').trim().split(/\r?\n/).map(line => line.trim()).filter(Boolean);
121
+ if (process.platform === 'win32') {
122
+ return lines.find(line => /\.(cmd|exe|bat)$/i.test(line)) || lines[0] || '';
123
+ }
124
+ return lines[0] || '';
125
+ }
126
+ function escapeCmdArg(value) {
127
+ const normalized = String(value ?? '');
128
+ if (!/[\s"]/u.test(normalized)) {
129
+ return normalized;
130
+ }
131
+ return `"${normalized.replace(/"/g, '""')}"`;
132
+ }
133
+ function runCodex(codexCommand, args, options) {
134
+ if (process.platform === 'win32' && /\.(cmd|bat)$/i.test(codexCommand)) {
135
+ const commandPrefix = /\s/u.test(codexCommand) ? `"${codexCommand}"` : codexCommand;
136
+ const commandLine = `${commandPrefix}${args.length > 0 ? ` ${args.map(escapeCmdArg).join(' ')}` : ''}`;
137
+ return spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', commandLine], {
138
+ ...options,
139
+ shell: false,
140
+ });
141
+ }
142
+ return spawnSync(codexCommand, args, {
143
+ ...options,
144
+ shell: false,
145
+ });
146
+ }
147
+ function classifyCodexCliFailure(details) {
148
+ const normalized = String(details || '').trim();
149
+ const lower = normalized.toLowerCase();
150
+ if (/authentication|login|unauthorized|forbidden|auth/i.test(normalized)) {
151
+ return {
152
+ code: 'auth',
153
+ message: 'Codex CLI authentication is not ready. Complete Codex CLI login/API configuration, then retry.',
154
+ };
155
+ }
156
+ if (/enotfound|eai_again|getaddrinfo|network|timed out|timeout|socket hang up/i.test(lower)) {
157
+ return {
158
+ code: 'network',
159
+ message: 'Codex CLI could not reach the upstream service. Check network/DNS access, then retry.',
160
+ };
161
+ }
162
+ if (/mcp|stitch/i.test(lower) && /not found|unknown|missing|failed/i.test(lower)) {
163
+ return {
164
+ code: 'mcp_unavailable',
165
+ message: 'Codex CLI could not use the stitch MCP server. Check ~/.codex/config.toml and MCP auth configuration, then retry.',
166
+ };
167
+ }
168
+ return {
169
+ code: 'generic',
170
+ message: 'Codex CLI exited with an error.',
171
+ };
172
+ }
173
+ function buildPrompt(changePath, projectPath, changeName, proposal, tasks, stateJson, canonicalProject) {
174
+ const prompt = [
175
+ 'You are the OSpec built-in Stitch adapter running through Codex CLI in non-interactive mode.',
176
+ 'Use the MCP server named "stitch" to create or update a reviewable page design preview for the current change.',
177
+ 'Do not ask the user questions. Do not return prose outside the required JSON.',
178
+ 'Return only one JSON object with this exact shape:',
179
+ '{"preview_url":"https://...","summary_markdown":"...","notes":"...","artifacts":[]}',
180
+ 'Rules:',
181
+ '- You must use the stitch MCP path, not a fake placeholder URL.',
182
+ '- preview_url must be a real review URL if the operation succeeds.',
183
+ '- summary_markdown should be reviewer-facing markdown summarizing the generated page and what to check.',
184
+ '- notes should be short and mention important assumptions or blockers.',
185
+ '- artifacts should be an array of objects or strings if any files/screenshots/URLs were produced; otherwise [].',
186
+ '',
187
+ `Project path: ${projectPath}`,
188
+ `Change path: ${changePath}`,
189
+ `Change name: ${changeName}`,
190
+ '',
191
+ 'Change state.json:',
192
+ '```json',
193
+ truncate(JSON.stringify(stateJson || {}, null, 2), 12000),
194
+ '```',
195
+ '',
196
+ 'proposal.md:',
197
+ '```markdown',
198
+ truncate(proposal, 16000),
199
+ '```',
200
+ '',
201
+ 'tasks.md:',
202
+ '```markdown',
203
+ truncate(tasks, 12000),
204
+ '```',
205
+ ];
206
+ if (canonicalProject?.projectId) {
207
+ prompt.push('', 'Canonical Stitch project requirements:', `- Reuse the existing Stitch project ID: ${canonicalProject.projectId}`);
208
+ if (canonicalProject.projectUrl) {
209
+ prompt.push(`- Existing canonical project URL: ${canonicalProject.projectUrl}`);
210
+ }
211
+ prompt.push('- Do not create a new Stitch project.');
212
+ prompt.push('- Add or update pages/nodes inside the existing project and return a preview URL from that same project ID.');
213
+ }
214
+ return prompt.join('\n');
215
+ }
216
+ function extractJsonCandidate(text) {
217
+ const normalized = String(text || '').trim();
218
+ if (!normalized) {
219
+ return null;
220
+ }
221
+ const fencedMatch = normalized.match(/```json\s*([\s\S]*?)```/i);
222
+ if (fencedMatch && fencedMatch[1]) {
223
+ return fencedMatch[1].trim();
224
+ }
225
+ const objectMatch = normalized.match(/\{[\s\S]*\}/);
226
+ if (objectMatch) {
227
+ return objectMatch[0].trim();
228
+ }
229
+ return null;
230
+ }
231
+ function normalizeStitchPreviewUrl(previewUrl) {
232
+ const normalized = String(previewUrl || '').trim();
233
+ if (!normalized) {
234
+ return {
235
+ value: '',
236
+ original: '',
237
+ normalized: false,
238
+ };
239
+ }
240
+ try {
241
+ const parsed = new URL(normalized);
242
+ if (parsed.hostname === 'stitch.canvas.google.com') {
243
+ const match = parsed.pathname.match(/^\/projects\/([^/]+)\/screens\/([^/?#]+)/i);
244
+ if (match) {
245
+ return {
246
+ value: `https://stitch.withgoogle.com/projects/${match[1]}?node-id=${match[2]}`,
247
+ original: normalized,
248
+ normalized: true,
249
+ };
250
+ }
251
+ }
252
+ if (parsed.hostname === 'stitch.withgoogle.com' || parsed.hostname === 'stitch.google.com') {
253
+ const match = parsed.pathname.match(/^\/projects\/([^/?#]+)/i);
254
+ if (match) {
255
+ const canonical = new URL(`https://stitch.withgoogle.com/projects/${match[1]}`);
256
+ const nodeId = parsed.searchParams.get('node-id') || parsed.searchParams.get('node_id') || '';
257
+ if (nodeId) {
258
+ canonical.searchParams.set('node-id', nodeId);
259
+ }
260
+ const nextValue = canonical.toString();
261
+ return {
262
+ value: nextValue,
263
+ original: normalized,
264
+ normalized: nextValue !== normalized,
265
+ };
266
+ }
267
+ }
268
+ }
269
+ catch {
270
+ }
271
+ return {
272
+ value: normalized,
273
+ original: normalized,
274
+ normalized: false,
275
+ };
276
+ }
277
+ function normalizeResult(parsed, fallbackResponse, requestedModel) {
278
+ const result = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
279
+ const rawPreviewUrl = typeof result.preview_url === 'string'
280
+ ? result.preview_url.trim()
281
+ : typeof result.previewUrl === 'string'
282
+ ? result.previewUrl.trim()
283
+ : '';
284
+ const summaryMarkdown = typeof result.summary_markdown === 'string'
285
+ ? result.summary_markdown.trim()
286
+ : typeof result.summaryMarkdown === 'string'
287
+ ? result.summaryMarkdown.trim()
288
+ : '';
289
+ const notes = typeof result.notes === 'string'
290
+ ? result.notes.trim()
291
+ : typeof result.message === 'string'
292
+ ? result.message.trim()
293
+ : '';
294
+ const artifacts = Array.isArray(result.artifacts) ? result.artifacts : [];
295
+ if (!rawPreviewUrl) {
296
+ const urlMatch = String(fallbackResponse || '').match(/https?:\/\/\S+/i);
297
+ if (urlMatch) {
298
+ const normalizedPreviewUrl = normalizeStitchPreviewUrl(urlMatch[0]);
299
+ return {
300
+ preview_url: normalizedPreviewUrl.value,
301
+ summary_markdown: summaryMarkdown,
302
+ notes,
303
+ artifacts,
304
+ metadata: {
305
+ adapter: 'codex-cli-stitch',
306
+ fallback_url_extracted: true,
307
+ ...(requestedModel ? { codex_model: requestedModel } : {}),
308
+ ...(normalizedPreviewUrl.normalized
309
+ ? { original_preview_url: normalizedPreviewUrl.original }
310
+ : {}),
311
+ },
312
+ };
313
+ }
314
+ fail('Codex response did not include preview_url.', truncate(String(fallbackResponse || ''), 1000));
315
+ }
316
+ const normalizedPreviewUrl = normalizeStitchPreviewUrl(rawPreviewUrl);
317
+ return {
318
+ preview_url: normalizedPreviewUrl.value,
319
+ summary_markdown: summaryMarkdown,
320
+ notes,
321
+ artifacts,
322
+ metadata: {
323
+ adapter: 'codex-cli-stitch',
324
+ ...(requestedModel ? { codex_model: requestedModel } : {}),
325
+ ...(normalizedPreviewUrl.normalized
326
+ ? { original_preview_url: normalizedPreviewUrl.original }
327
+ : {}),
328
+ },
329
+ };
330
+ }
331
+ const args = parseArgs(process.argv.slice(2));
332
+ const changePath = path.resolve(args.change || process.env.OSPEC_STITCH_CHANGE_PATH || process.cwd());
333
+ const projectPath = path.resolve(args.project || process.env.OSPEC_STITCH_PROJECT_PATH || process.cwd());
334
+ const requestedModel = String(args.model || process.env.OSPEC_STITCH_CODEX_MODEL || '').trim();
335
+ const bypassApprovals = parseBooleanFlag(args['dangerously-bypass-approvals-and-sandbox']
336
+ || process.env.OSPEC_STITCH_CODEX_BYPASS_APPROVALS_AND_SANDBOX, true);
337
+ const codexCommand = resolveCodexCommand(projectPath);
338
+ if (!codexCommand) {
339
+ fail('Codex CLI is not available. Install it first.', '', 'missing_cli');
340
+ }
341
+ const codexCheck = runCodex(codexCommand, ['--version'], {
342
+ cwd: projectPath,
343
+ encoding: 'utf8',
344
+ });
345
+ if (codexCheck.error || codexCheck.status !== 0) {
346
+ fail('Codex CLI is not available. Install it first.', '', 'missing_cli');
347
+ }
348
+ const codexSettings = inspectCodexSettings();
349
+ if (!codexSettings.exists) {
350
+ fail(`Codex config not found at ${codexSettings.settingsPath}. Configure Codex CLI and the stitch MCP server first.`, '', 'missing_config');
351
+ }
352
+ if (!codexSettings.stitchConfigured) {
353
+ fail(`Codex stitch MCP is not configured in ${codexSettings.settingsPath}. Add [mcp_servers.stitch] before using the built-in adapter.`, '', 'missing_mcp');
354
+ }
355
+ if (!codexSettings.stitchTransportHttp) {
356
+ fail(`Codex stitch MCP in ${codexSettings.settingsPath} must set type = "http". Follow the Codex snippet from docs/stitch-plugin-spec.zh-CN.md.`, '', 'missing_transport');
357
+ }
358
+ if (!codexSettings.stitchUrlConfigured) {
359
+ fail(`Codex stitch MCP in ${codexSettings.settingsPath} must set url = "https://stitch.googleapis.com/mcp". Follow the Codex snippet from docs/stitch-plugin-spec.zh-CN.md.`, '', 'missing_url');
360
+ }
361
+ if (!codexSettings.stitchAuthConfigured) {
362
+ fail(`Codex stitch MCP in ${codexSettings.settingsPath} must set X-Goog-Api-Key in headers or [mcp_servers.stitch.http_headers]. Follow the Codex snippet from docs/stitch-plugin-spec.zh-CN.md.`, '', 'missing_auth');
363
+ }
364
+ const statePath = path.join(changePath, 'state.json');
365
+ const proposalPath = path.join(changePath, 'proposal.md');
366
+ const tasksPath = path.join(changePath, 'tasks.md');
367
+ const stateJson = readJsonIfExists(statePath);
368
+ const changeName = stateJson?.feature || path.basename(changePath);
369
+ const canonicalProject = {
370
+ projectId: String(process.env.OSPEC_STITCH_CANONICAL_PROJECT_ID || '').trim(),
371
+ projectUrl: String(process.env.OSPEC_STITCH_CANONICAL_PROJECT_URL || '').trim(),
372
+ };
373
+ const prompt = buildPrompt(changePath, projectPath, changeName, readFileIfExists(proposalPath), readFileIfExists(tasksPath), stateJson, canonicalProject);
374
+ const outputPath = path.join(os.tmpdir(), `ospec-codex-stitch-${process.pid}-${Date.now()}.txt`);
375
+ const codexArgs = ['exec', '--skip-git-repo-check', '-C', projectPath];
376
+ if (bypassApprovals) {
377
+ codexArgs.push('--dangerously-bypass-approvals-and-sandbox');
378
+ }
379
+ if (requestedModel) {
380
+ codexArgs.push('-m', requestedModel);
381
+ }
382
+ codexArgs.push('-o', outputPath, '-');
383
+ const runResult = runCodex(codexCommand, codexArgs, {
384
+ cwd: projectPath,
385
+ encoding: 'utf8',
386
+ input: prompt,
387
+ maxBuffer: 10 * 1024 * 1024,
388
+ env: {
389
+ ...process.env,
390
+ OSPEC_STITCH_CHANGE_PATH: changePath,
391
+ OSPEC_STITCH_PROJECT_PATH: projectPath,
392
+ },
393
+ });
394
+ if (runResult.error) {
395
+ fail('Failed to execute Codex CLI.', runResult.error.message, 'generic');
396
+ }
397
+ if (runResult.status !== 0) {
398
+ const details = String(runResult.stderr || runResult.stdout || '').trim();
399
+ const failure = classifyCodexCliFailure(details);
400
+ fail(failure.message, truncate(details, 1200), failure.code);
401
+ }
402
+ const rawResponse = readFileIfExists(outputPath) || String(runResult.stdout || '').trim();
403
+ try {
404
+ if (fs.existsSync(outputPath)) {
405
+ fs.unlinkSync(outputPath);
406
+ }
407
+ }
408
+ catch {
409
+ }
410
+ const responseJsonBlock = extractJsonCandidate(rawResponse);
411
+ let parsedResponse = null;
412
+ if (responseJsonBlock) {
413
+ try {
414
+ parsedResponse = JSON.parse(responseJsonBlock);
415
+ }
416
+ catch {
417
+ parsedResponse = null;
418
+ }
419
+ }
420
+ process.stdout.write(`${JSON.stringify(normalizeResult(parsedResponse, rawResponse, requestedModel))}\n`);