@codeyam/codeyam-cli 0.1.0-staging.0a69043 → 0.1.0-staging.0f367d6
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.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +2 -2
- package/analyzer-template/packages/aws/package.json +5 -5
- package/analyzer-template/packages/database/package.json +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +39 -3
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +701 -94
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +20 -0
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/data/techStacks.js +1 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js +93 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +146 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +1108 -0
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +120 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/envFile.test.js +125 -0
- package/codeyam-cli/src/utils/__tests__/envFile.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js +500 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +0 -1
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
- package/codeyam-cli/src/utils/devServerState.js +32 -0
- package/codeyam-cli/src/utils/devServerState.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +28 -6
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
- package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +27 -12
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +11 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +27 -14
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +15 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/envFile.js +90 -0
- package/codeyam-cli/src/utils/envFile.js.map +1 -0
- package/codeyam-cli/src/utils/handoffContext.js +257 -0
- package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +36 -6
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/techStackConfig.js +38 -0
- package/codeyam-cli/src/utils/techStackConfig.js.map +1 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js +85 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.js +53 -0
- package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
- package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
- package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
- package/codeyam-cli/src/utils/testRunner.js +1 -7
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/utils/webappDetection.js +4 -2
- package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +1 -0
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +119 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +354 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +32 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CLe80MMu.js → CopyButton-DTBZZfSk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-Crt_KN_U.js → EntityItem-BxclONWq.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CD7lGABo.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-CgTNOhnu.js → InlineSpinner-ByaELMbv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-DtYTSPL2.js → InteractivePreview-6WjVfhxX.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-D3s1MFkb.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CM5zg40N.js → LogViewer-C-9zQdXg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{MiniClaudeChat-CQENLSrF.js → MiniClaudeChat-Bs2_Oua4.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C2PLkej3.js → ReportIssueModal-DQsceHVv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DanvyBPb.js → SafeScreenshot-DThcm_9M.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-CefgqbCr.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{Spinner-Bc8BG-Lw.js → Spinner-CIil5-gb.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-BA_Ry-rs.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-C1YkzTAV.js → _index-DnOgyseQ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-yH46LLUz.js → activity.(_tab)-DqM9hbNE.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-CHx25PAe.js → addon-web-links-C58dYPwR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bg3e7q4S.js → agent-transcripts-B8NCeOrm.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-database-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-github-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-handoff-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-hosting-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-CL-lMgHh.js → book-open-BFSIqZgO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-GmAjGS9-.js → chevron-down-B9fDzFVh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DFcQkN5j.js → circle-check-DLPObLUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-C6iF61Xs.js → copy-DXEmO0TD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-4ImjHTVC.js → createLucideIcon-BwyFiRot.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CRepiabR.js → dev.empty-iRhRIFlp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DhtVC4aI.js +161 -0
- package/codeyam-cli/src/webserver/build/client/assets/{editorPreview-CluPkvXJ.js → editorPreview-C6fEYHrh.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DYJRGiDI.js → entity._sha._-pc-vc6wO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-wdiwx5-Z.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-BrkN-40Y.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DxfhekTZ.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CRXJWmpB.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-SuW9syRS.js → entry.client-j1Vi0bco.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{files-D-xGrg29.js → files-kuny2Q_s.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-Bq_fbXP5.js → git-DgCZPMie.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-Bp1l4hSv.js → index-BliGSSpl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-DE3jI_dv.js → index-SqjQKTdH.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-CWV9XZiG.js → index-vyrZD2g4.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{labs-B_IX45ih.js → labs-c3yLxSEp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-De-7qQ2u.js → loader-circle-D-q28GLF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-79d0d81a.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-Cx2xEx7s.js → memory-CEWIUC4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{pause-CFxEKL1u.js → pause-BP6fitdh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-DXjFYOxD.js → root-L2V0jea7.js} +4 -4
- package/codeyam-cli/src/webserver/build/client/assets/{search-BdBb5aqc.js → search-BooqacKS.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{settings-DdE-Untf.js → settings-BM0nbryO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-DSCdE99u.js → simulations-ovy6FjRY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-CrplD4b1.js → terminal-DHemCJIs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DqJ0j69l.js → triangle-alert-D87ekDl8.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DhXHbEjP.js → useCustomSizes-Dk0Tciqg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-D9QZKaLJ.js → useLastLogLine-C8QvIe05.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-Cy5Qg_UR.js → useReportContext-jkCytuYz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-5HR2j9ZE.js → useToast-BgqkixU9.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{analysisRunner-zEYtiv0T.js → analysisRunner-QgInFGdU.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{index-CcHPEbhi.js → index-zblh9auj.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-DaE0CBjk.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CNvgz1cC.js +853 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +255 -20
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +12 -3
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/server.js +67 -0
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +105 -12
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
- package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
- package/codeyam-cli/templates/editor-step-hook.py +4 -4
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
- package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
- package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
- package/codeyam-cli/templates/expo-react-native/app.json +11 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
- package/codeyam-cli/templates/expo-react-native/global.css +7 -0
- package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
- package/codeyam-cli/templates/expo-react-native/package.json +32 -16
- package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
- package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/seed-adapters/supabase.ts +261 -77
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +15 -0
- package/package.json +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +0 -43
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-oBrbke_R.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/globals-oyPmV37k.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-694b698a.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-D68IyWbU.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Cxzo0Zp2.js +0 -688
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
|
@@ -24,7 +24,9 @@ import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from ".
|
|
|
24
24
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
25
25
|
import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
|
|
26
26
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
27
|
+
import { updateHandoffProgress } from "../utils/handoffContext.js";
|
|
27
28
|
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
29
|
+
import { getDisplayVersion } from "../utils/versionInfo.js";
|
|
28
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
31
|
const __dirname = path.dirname(__filename);
|
|
30
32
|
const STEP_LABELS = {
|
|
@@ -189,6 +191,181 @@ function getProjectDimensions(root) {
|
|
|
189
191
|
return { defaultName: 'Desktop', names: [] };
|
|
190
192
|
}
|
|
191
193
|
}
|
|
194
|
+
function getTechStackContext(root) {
|
|
195
|
+
const state = readState(root);
|
|
196
|
+
const techStackId = state?.techStackId || '';
|
|
197
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
198
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
199
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
200
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
201
|
+
return {
|
|
202
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
203
|
+
isExpo,
|
|
204
|
+
isNextjs,
|
|
205
|
+
isChromeExt,
|
|
206
|
+
hasDatabase: isNextjs,
|
|
207
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
208
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
209
|
+
storageType: isExpo
|
|
210
|
+
? 'asyncStorage'
|
|
211
|
+
: isChromeExt
|
|
212
|
+
? 'chromeStorage'
|
|
213
|
+
: 'prisma',
|
|
214
|
+
routerImport: isExpo
|
|
215
|
+
? 'expo-router'
|
|
216
|
+
: isChromeExt
|
|
217
|
+
? 'react-router-dom'
|
|
218
|
+
: 'next/navigation',
|
|
219
|
+
componentPrimitives: isExpo
|
|
220
|
+
? '<View>, <Text>, <ScrollView>'
|
|
221
|
+
: '<div>, <span>, <h1>',
|
|
222
|
+
rawPrimitivesList: isExpo
|
|
223
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
224
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
225
|
+
patternsFile: isExpo
|
|
226
|
+
? 'MOBILE_SETUP.md'
|
|
227
|
+
: isChromeExt
|
|
228
|
+
? 'EXTENSION_SETUP.md'
|
|
229
|
+
: 'FEATURE_PATTERNS.md',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns a pre-populated tech stack for a given template ID.
|
|
234
|
+
* Written to .codeyam/config.json during `codeyam editor template`.
|
|
235
|
+
*/
|
|
236
|
+
function getTechStackForTemplate(templateId) {
|
|
237
|
+
switch (templateId) {
|
|
238
|
+
case 'nextjs-prisma-sqlite':
|
|
239
|
+
return {
|
|
240
|
+
languages: [
|
|
241
|
+
{
|
|
242
|
+
name: 'TypeScript',
|
|
243
|
+
url: 'https://typescriptlang.org',
|
|
244
|
+
description: 'Statically typed JavaScript superset',
|
|
245
|
+
version: '5',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
frameworks: [
|
|
249
|
+
{
|
|
250
|
+
name: 'Next.js',
|
|
251
|
+
url: 'https://nextjs.org',
|
|
252
|
+
description: 'Full-stack React framework with App Router, SSR, and API routes',
|
|
253
|
+
version: '15',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'React',
|
|
257
|
+
url: 'https://react.dev',
|
|
258
|
+
description: 'Component-based UI library',
|
|
259
|
+
version: '19',
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
databases: [
|
|
263
|
+
{
|
|
264
|
+
name: 'SQLite',
|
|
265
|
+
url: 'https://sqlite.org',
|
|
266
|
+
description: 'Embedded relational database — zero config, upgradeable to hosted DB',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
libraries: [
|
|
270
|
+
{
|
|
271
|
+
name: 'Prisma',
|
|
272
|
+
url: 'https://prisma.io',
|
|
273
|
+
description: 'Type-safe database ORM and query builder',
|
|
274
|
+
version: '7',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'Tailwind CSS',
|
|
278
|
+
url: 'https://tailwindcss.com',
|
|
279
|
+
description: 'Utility-first CSS framework',
|
|
280
|
+
version: '4',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
case 'chrome-extension-react':
|
|
285
|
+
return {
|
|
286
|
+
languages: [
|
|
287
|
+
{
|
|
288
|
+
name: 'TypeScript',
|
|
289
|
+
url: 'https://typescriptlang.org',
|
|
290
|
+
description: 'Statically typed JavaScript superset',
|
|
291
|
+
version: '5',
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
frameworks: [
|
|
295
|
+
{
|
|
296
|
+
name: 'React',
|
|
297
|
+
url: 'https://react.dev',
|
|
298
|
+
description: 'Component-based UI library for the popup and options pages',
|
|
299
|
+
version: '19',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'Vite',
|
|
303
|
+
url: 'https://vite.dev',
|
|
304
|
+
description: 'Fast build tool and dev server',
|
|
305
|
+
version: '6',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
libraries: [
|
|
309
|
+
{
|
|
310
|
+
name: 'Tailwind CSS',
|
|
311
|
+
url: 'https://tailwindcss.com',
|
|
312
|
+
description: 'Utility-first CSS framework',
|
|
313
|
+
version: '4',
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
infrastructure: [
|
|
317
|
+
{
|
|
318
|
+
name: 'Chrome Manifest V3',
|
|
319
|
+
url: 'https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3',
|
|
320
|
+
description: 'Extension platform with service workers, declarative APIs, and chrome.storage',
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
case 'expo-react-native':
|
|
325
|
+
return {
|
|
326
|
+
languages: [
|
|
327
|
+
{
|
|
328
|
+
name: 'TypeScript',
|
|
329
|
+
url: 'https://typescriptlang.org',
|
|
330
|
+
description: 'Statically typed JavaScript superset',
|
|
331
|
+
version: '5',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
frameworks: [
|
|
335
|
+
{
|
|
336
|
+
name: 'Expo',
|
|
337
|
+
url: 'https://expo.dev',
|
|
338
|
+
description: 'React Native development platform with managed workflow and OTA updates',
|
|
339
|
+
version: 'SDK 54',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'React Native',
|
|
343
|
+
url: 'https://reactnative.dev',
|
|
344
|
+
description: 'Cross-platform mobile UI framework',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'Expo Router',
|
|
348
|
+
url: 'https://docs.expo.dev/router/introduction/',
|
|
349
|
+
description: 'File-based navigation for React Native apps',
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
libraries: [
|
|
353
|
+
{
|
|
354
|
+
name: 'NativeWind',
|
|
355
|
+
url: 'https://www.nativewind.dev',
|
|
356
|
+
description: 'Tailwind CSS for React Native',
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'AsyncStorage',
|
|
360
|
+
url: 'https://react-native-async-storage.github.io/async-storage/',
|
|
361
|
+
description: 'Persistent key-value storage for React Native',
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
default:
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
192
369
|
/**
|
|
193
370
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
194
371
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -212,11 +389,63 @@ function checkbox(text) {
|
|
|
212
389
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
213
390
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
214
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Print a checklist item for the handoff summary.
|
|
394
|
+
*/
|
|
395
|
+
function checkboxHandoff() {
|
|
396
|
+
checkbox('Update the handoff summary in `.codeyam/config.json` for the next session/AI: `codeyam editor handoff \'{"summary":"..."}\'`');
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Print the handoff context from a previous session or AI provider.
|
|
400
|
+
* Prefers the auto-generated handoff-context.md over the raw config summary.
|
|
401
|
+
*/
|
|
402
|
+
function printHandoffContext(root) {
|
|
403
|
+
try {
|
|
404
|
+
// Check for auto-generated handoff context (richer, includes progress timeline)
|
|
405
|
+
const handoffContextPath = path.join(root, '.codeyam', 'handoff-context.md');
|
|
406
|
+
if (fs.existsSync(handoffContextPath)) {
|
|
407
|
+
const content = fs.readFileSync(handoffContextPath, 'utf8');
|
|
408
|
+
console.log();
|
|
409
|
+
console.log(chalk.bold.magenta('━━━ PROVIDER HANDOFF CONTEXT ━━━'));
|
|
410
|
+
console.log(chalk.magenta(content));
|
|
411
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
412
|
+
console.log();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Fall back to config.json handoff summary
|
|
416
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
417
|
+
if (!fs.existsSync(configPath))
|
|
418
|
+
return;
|
|
419
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
420
|
+
const handoff = config.handoff;
|
|
421
|
+
if (!handoff || !handoff.summary)
|
|
422
|
+
return;
|
|
423
|
+
console.log();
|
|
424
|
+
console.log(chalk.bold.magenta(`━━━ HANDOFF FROM ${String(handoff.lastProvider || 'unknown').toUpperCase()} (Step ${handoff.lastStep || 'unknown'}) ━━━`));
|
|
425
|
+
console.log(chalk.magenta(handoff.summary));
|
|
426
|
+
if (handoff.lastUpdated) {
|
|
427
|
+
console.log(chalk.dim(` Updated: ${handoff.lastUpdated}`));
|
|
428
|
+
}
|
|
429
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
430
|
+
console.log();
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// Non-fatal
|
|
434
|
+
}
|
|
435
|
+
}
|
|
215
436
|
/**
|
|
216
437
|
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
217
438
|
* Only prints creation instructions if the file doesn't exist yet.
|
|
218
439
|
* If it exists, reminds Claude to update it if data models changed.
|
|
219
440
|
*/
|
|
441
|
+
function printServiceRecordingReminder(port) {
|
|
442
|
+
console.log(chalk.bold('Third-party services:'));
|
|
443
|
+
checkbox('When integrating any external service (auth, email, payments, storage, telemetry), update the tech stack');
|
|
444
|
+
console.log(chalk.dim(' Include the service name, URL, description, and envKeys (environment variable names it requires).'));
|
|
445
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
446
|
+
console.log(chalk.dim(` -d '{"techStack": {"services": [{"name":"Stripe","url":"https://stripe.com","description":"Payments","envKeys":["STRIPE_SECRET_KEY","STRIPE_PUBLISHABLE_KEY"]}]}}'`));
|
|
447
|
+
console.log(chalk.dim(' This ensures the deploy roadmap tracks all services that need production credentials.'));
|
|
448
|
+
}
|
|
220
449
|
function printDataStructureInstructions() {
|
|
221
450
|
const root = getProjectRoot();
|
|
222
451
|
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
@@ -267,6 +496,10 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
267
496
|
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
268
497
|
}
|
|
269
498
|
console.log();
|
|
499
|
+
checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
|
|
500
|
+
console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
|
|
501
|
+
console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
|
|
502
|
+
console.log();
|
|
270
503
|
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
271
504
|
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
272
505
|
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
@@ -281,7 +514,12 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
281
514
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
282
515
|
}
|
|
283
516
|
else {
|
|
517
|
+
const appCtx = getTechStackContext(root);
|
|
284
518
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
519
|
+
if (appCtx.isExpo) {
|
|
520
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
521
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
522
|
+
}
|
|
285
523
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
286
524
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
287
525
|
}
|
|
@@ -329,6 +567,12 @@ function printExtractionPlanInstructions() {
|
|
|
329
567
|
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
330
568
|
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
331
569
|
console.log();
|
|
570
|
+
console.log(chalk.bold.red('THE DECOMPOSITION RULE: All code should do one thing.'));
|
|
571
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces to form a coordinated whole.'));
|
|
572
|
+
console.log(chalk.yellow(' Every opportunity to extract code into a sensible function, helper, or sub-component MUST be taken.'));
|
|
573
|
+
console.log(chalk.yellow(' Then extract AGAIN from the extracted code — keep going until each piece does one clearly defined thing.'));
|
|
574
|
+
console.log(chalk.yellow(' This applies to ALL code: backend routes, business logic, frontend components, utilities — everything.'));
|
|
575
|
+
console.log();
|
|
332
576
|
console.log(chalk.bold('Checklist:'));
|
|
333
577
|
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
334
578
|
checkbox('Read EVERY file used by this page/feature');
|
|
@@ -339,6 +583,12 @@ function printExtractionPlanInstructions() {
|
|
|
339
583
|
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
340
584
|
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
341
585
|
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
586
|
+
console.log(chalk.yellow(' Inline logic — backend AND frontend (MUST be extracted as named functions):'));
|
|
587
|
+
console.log(chalk.yellow(' conditionals/ternaries, computed/derived values, data transformations,'));
|
|
588
|
+
console.log(chalk.yellow(' string formatting/interpolation, array filtering/sorting/mapping callbacks,'));
|
|
589
|
+
console.log(chalk.yellow(' default value logic, null/undefined guards, date/number formatting,'));
|
|
590
|
+
console.log(chalk.yellow(' permission/role checks, status derivation (e.g. isOverdue(task)),'));
|
|
591
|
+
console.log(chalk.yellow(' request/response shaping, error message construction, config lookups'));
|
|
342
592
|
console.log();
|
|
343
593
|
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
344
594
|
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
@@ -349,6 +599,15 @@ function printExtractionPlanInstructions() {
|
|
|
349
599
|
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
350
600
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
351
601
|
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
602
|
+
console.log(chalk.yellow(' — How you will test it (key inputs/outputs and edge cases)'));
|
|
603
|
+
console.log(chalk.yellow(' — What logic inside it can be further extracted as a separate testable function'));
|
|
604
|
+
console.log();
|
|
605
|
+
console.log(chalk.bold.red('BEFORE FINALIZING THE PLAN:'));
|
|
606
|
+
console.log(chalk.yellow(' Re-read every file ONE MORE TIME. For each piece of code, ask:'));
|
|
607
|
+
console.log(chalk.yellow(' 1. Does this do more than one thing? → Split it into pieces that each do one thing.'));
|
|
608
|
+
console.log(chalk.yellow(' 2. Is there logic here that could be its own function with clear inputs/outputs? → Extract it.'));
|
|
609
|
+
console.log(chalk.yellow(' 3. Can the extracted code itself be broken down further? → Keep extracting.'));
|
|
610
|
+
console.log(chalk.yellow(' If your plan has fewer functions than components, you probably missed extractable logic.'));
|
|
352
611
|
console.log();
|
|
353
612
|
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
354
613
|
}
|
|
@@ -356,10 +615,18 @@ function printExtractionPlanInstructions() {
|
|
|
356
615
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
357
616
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
358
617
|
*/
|
|
359
|
-
function printComponentCaptureInstructions() {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
618
|
+
function printComponentCaptureInstructions(root) {
|
|
619
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
620
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
621
|
+
checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
622
|
+
if (isExpo) {
|
|
623
|
+
console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
|
|
624
|
+
console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
|
|
628
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
629
|
+
}
|
|
363
630
|
checkbox('For each visual component:');
|
|
364
631
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
365
632
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -370,14 +637,28 @@ function printComponentCaptureInstructions() {
|
|
|
370
637
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
371
638
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
372
639
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
373
|
-
|
|
374
|
-
|
|
640
|
+
if (isExpo) {
|
|
641
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
642
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
646
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
647
|
+
}
|
|
375
648
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
376
649
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
650
|
+
if (isExpo) {
|
|
651
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
652
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
656
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
657
|
+
}
|
|
658
|
+
console.log(chalk.dim(isExpo
|
|
659
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
660
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
661
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
381
662
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
382
663
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
383
664
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
@@ -746,8 +1027,8 @@ function printSetup(root) {
|
|
|
746
1027
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
747
1028
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
748
1029
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
749
|
-
console.log(chalk.dim('
|
|
750
|
-
console.log(chalk.dim('
|
|
1030
|
+
console.log(chalk.dim(' If only ONE stack matches, confirm it directly (e.g. "Expo + React Native is the only stack for mobile apps — using that.").'));
|
|
1031
|
+
console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
|
|
751
1032
|
console.log();
|
|
752
1033
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
753
1034
|
for (const stack of TECH_STACKS) {
|
|
@@ -780,7 +1061,7 @@ function printSetup(root) {
|
|
|
780
1061
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
781
1062
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
782
1063
|
console.log();
|
|
783
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
1064
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
|
|
784
1065
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
785
1066
|
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
786
1067
|
console.log();
|
|
@@ -939,6 +1220,15 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
939
1220
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
940
1221
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
941
1222
|
console.log();
|
|
1223
|
+
checkbox('Detect and set the project tech stack:');
|
|
1224
|
+
console.log(chalk.dim(' Scan package.json, framework configs (.env, .env.example, tsconfig.json, next.config.*, prisma/schema.prisma, etc.)'));
|
|
1225
|
+
console.log(chalk.dim(' to detect languages, frameworks, databases, services, libraries, and infrastructure.'));
|
|
1226
|
+
console.log(chalk.dim(' Each item MUST have name, url, and description. version and envKeys are optional.'));
|
|
1227
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
1228
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
1229
|
+
console.log(chalk.dim(' -d \'{"techStack":{"languages":[{"name":"TypeScript","url":"https://typescriptlang.org","description":"Statically typed JavaScript superset","version":"5"}],"frameworks":[{"name":"Next.js","url":"https://nextjs.org","description":"Full-stack React framework with SSR and API routes","version":"15"}],"databases":[{"name":"SQLite","url":"https://sqlite.org","description":"Embedded relational database"}],"services":[{"name":"Stripe","url":"https://stripe.com","description":"Payment processing and billing","envKeys":["STRIPE_SECRET_KEY"]}],"libraries":[{"name":"Prisma","url":"https://prisma.io","description":"Type-safe database ORM","version":"7"}],"infrastructure":[]}}\''));
|
|
1230
|
+
console.log(chalk.dim(' Replace the example values with what you detect in this project. Omit empty categories.'));
|
|
1231
|
+
console.log();
|
|
942
1232
|
const designSystem = readDesignSystem(root);
|
|
943
1233
|
if (designSystem) {
|
|
944
1234
|
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
@@ -952,6 +1242,8 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
952
1242
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
953
1243
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
954
1244
|
console.log();
|
|
1245
|
+
checkboxHandoff();
|
|
1246
|
+
console.log();
|
|
955
1247
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
956
1248
|
console.log();
|
|
957
1249
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
@@ -989,33 +1281,79 @@ function printStep2(root, feature) {
|
|
|
989
1281
|
}
|
|
990
1282
|
console.log('Get the project ready to build.');
|
|
991
1283
|
console.log();
|
|
1284
|
+
const ctx = getTechStackContext(root);
|
|
992
1285
|
// If no project exists yet, include scaffolding instructions first
|
|
993
1286
|
if (!projectExists) {
|
|
994
1287
|
console.log(chalk.bold('Scaffold the project:'));
|
|
995
1288
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
996
|
-
|
|
1289
|
+
if (ctx.isExpo) {
|
|
1290
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1291
|
+
}
|
|
1292
|
+
else if (ctx.isChromeExt) {
|
|
1293
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1297
|
+
}
|
|
997
1298
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
998
1299
|
console.log();
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1300
|
+
if (ctx.isExpo) {
|
|
1301
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1302
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1303
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1304
|
+
console.log();
|
|
1305
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1306
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1307
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1308
|
+
console.log();
|
|
1309
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1310
|
+
console.log();
|
|
1311
|
+
checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
|
|
1312
|
+
console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
|
|
1313
|
+
console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
|
|
1314
|
+
port +
|
|
1315
|
+
'/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
|
|
1316
|
+
console.log();
|
|
1317
|
+
checkbox('Generate an app icon using `sharp`');
|
|
1318
|
+
console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
|
|
1319
|
+
console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
|
|
1320
|
+
console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
|
|
1321
|
+
console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
|
|
1322
|
+
console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
|
|
1323
|
+
console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
|
|
1324
|
+
console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
|
|
1325
|
+
console.log();
|
|
1326
|
+
}
|
|
1327
|
+
else if (ctx.isChromeExt) {
|
|
1328
|
+
// Chrome Extension: use chrome.storage
|
|
1329
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1330
|
+
console.log();
|
|
1331
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1332
|
+
console.log();
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
// Next.js: Prisma + database
|
|
1336
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1337
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1338
|
+
console.log();
|
|
1339
|
+
checkbox('Push schema and seed the database');
|
|
1340
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1341
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1342
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1343
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1344
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1345
|
+
console.log();
|
|
1346
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1347
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1348
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1349
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1350
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1351
|
+
console.log();
|
|
1352
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1353
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1354
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1355
|
+
console.log();
|
|
1356
|
+
}
|
|
1019
1357
|
printDataStructureInstructions();
|
|
1020
1358
|
}
|
|
1021
1359
|
else {
|
|
@@ -1033,6 +1371,10 @@ function printStep2(root, feature) {
|
|
|
1033
1371
|
console.log();
|
|
1034
1372
|
printDataStructureInstructions();
|
|
1035
1373
|
}
|
|
1374
|
+
console.log();
|
|
1375
|
+
printServiceRecordingReminder(port);
|
|
1376
|
+
console.log();
|
|
1377
|
+
checkboxHandoff();
|
|
1036
1378
|
stopGate(2);
|
|
1037
1379
|
}
|
|
1038
1380
|
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
@@ -1057,26 +1399,42 @@ function printStep3(root, feature) {
|
|
|
1057
1399
|
if (isResuming) {
|
|
1058
1400
|
printResumptionHeader(3);
|
|
1059
1401
|
}
|
|
1402
|
+
const ctx = getTechStackContext(root);
|
|
1060
1403
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1061
1404
|
console.log();
|
|
1062
1405
|
console.log(chalk.bold('Checklist:'));
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1406
|
+
if (ctx.isExpo) {
|
|
1407
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1408
|
+
if (!projectExists) {
|
|
1409
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1410
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1411
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1412
|
+
console.log();
|
|
1413
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1414
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1415
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1416
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1421
|
+
if (!projectExists) {
|
|
1422
|
+
checkbox('Seed the database with demo data');
|
|
1423
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1424
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1425
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1426
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1427
|
+
console.log();
|
|
1428
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1429
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1430
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1431
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1432
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1433
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1434
|
+
}
|
|
1077
1435
|
}
|
|
1078
1436
|
checkbox('Verify the dev server shows the changes');
|
|
1079
|
-
checkbox(
|
|
1437
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1080
1438
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1081
1439
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1082
1440
|
console.log();
|
|
@@ -1093,16 +1451,33 @@ function printStep3(root, feature) {
|
|
|
1093
1451
|
console.log();
|
|
1094
1452
|
console.log(designSystem);
|
|
1095
1453
|
console.log();
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1454
|
+
if (ctx.isExpo) {
|
|
1455
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1456
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1457
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1458
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1459
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1460
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1461
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1462
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1463
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1464
|
+
checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
|
|
1465
|
+
console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
|
|
1466
|
+
console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
|
|
1467
|
+
console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1471
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1472
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1473
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1474
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1475
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1476
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1477
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1478
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1479
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1480
|
+
}
|
|
1106
1481
|
}
|
|
1107
1482
|
console.log();
|
|
1108
1483
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1116,6 +1491,9 @@ function printStep3(root, feature) {
|
|
|
1116
1491
|
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1117
1492
|
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1118
1493
|
console.log();
|
|
1494
|
+
printServiceRecordingReminder(port);
|
|
1495
|
+
console.log();
|
|
1496
|
+
checkboxHandoff();
|
|
1119
1497
|
stopGate(3);
|
|
1120
1498
|
}
|
|
1121
1499
|
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
@@ -1130,6 +1508,8 @@ function printStep4(root, feature) {
|
|
|
1130
1508
|
label: STEP_LABELS[4],
|
|
1131
1509
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1132
1510
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1511
|
+
appFormats: prevState?.appFormats,
|
|
1512
|
+
techStackId: prevState?.techStackId,
|
|
1133
1513
|
});
|
|
1134
1514
|
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1135
1515
|
stepHeader(4, 'Verify Prototype', feature);
|
|
@@ -1156,6 +1536,14 @@ function printStep4(root, feature) {
|
|
|
1156
1536
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1157
1537
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1158
1538
|
console.log();
|
|
1539
|
+
const ctx4 = getTechStackContext(root);
|
|
1540
|
+
if (ctx4.isExpo) {
|
|
1541
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1542
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1543
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1544
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1545
|
+
console.log();
|
|
1546
|
+
}
|
|
1159
1547
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1160
1548
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1161
1549
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1163,6 +1551,8 @@ function printStep4(root, feature) {
|
|
|
1163
1551
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1164
1552
|
console.log();
|
|
1165
1553
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1554
|
+
console.log();
|
|
1555
|
+
checkboxHandoff();
|
|
1166
1556
|
stopGate(4);
|
|
1167
1557
|
}
|
|
1168
1558
|
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
@@ -1178,6 +1568,8 @@ function printStep5(root, feature) {
|
|
|
1178
1568
|
label: STEP_LABELS[5],
|
|
1179
1569
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1180
1570
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1571
|
+
appFormats: prevState?.appFormats,
|
|
1572
|
+
techStackId: prevState?.techStackId,
|
|
1181
1573
|
});
|
|
1182
1574
|
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1183
1575
|
stepHeader(5, 'Confirm', feature);
|
|
@@ -1224,6 +1616,8 @@ function printStep5(root, feature) {
|
|
|
1224
1616
|
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1225
1617
|
console.log();
|
|
1226
1618
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1619
|
+
console.log();
|
|
1620
|
+
checkboxHandoff();
|
|
1227
1621
|
stopGate(5, { confirm: true });
|
|
1228
1622
|
}
|
|
1229
1623
|
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
@@ -1237,6 +1631,8 @@ function printStep6(root, feature) {
|
|
|
1237
1631
|
label: STEP_LABELS[6],
|
|
1238
1632
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1239
1633
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1634
|
+
appFormats: prevState?.appFormats,
|
|
1635
|
+
techStackId: prevState?.techStackId,
|
|
1240
1636
|
});
|
|
1241
1637
|
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1242
1638
|
stepHeader(6, 'Deconstruct', feature);
|
|
@@ -1247,6 +1643,8 @@ function printStep6(root, feature) {
|
|
|
1247
1643
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1248
1644
|
console.log();
|
|
1249
1645
|
printExtractionPlanInstructions();
|
|
1646
|
+
console.log();
|
|
1647
|
+
checkboxHandoff();
|
|
1250
1648
|
stopGate(6);
|
|
1251
1649
|
}
|
|
1252
1650
|
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
@@ -1262,12 +1660,15 @@ function printStep7(root, feature) {
|
|
|
1262
1660
|
label: STEP_LABELS[7],
|
|
1263
1661
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1264
1662
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1663
|
+
appFormats: prevState?.appFormats,
|
|
1664
|
+
techStackId: prevState?.techStackId,
|
|
1265
1665
|
});
|
|
1266
1666
|
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1267
1667
|
stepHeader(7, 'Extract', feature);
|
|
1268
1668
|
if (isResuming) {
|
|
1269
1669
|
printResumptionHeader(7);
|
|
1270
1670
|
}
|
|
1671
|
+
const ctx = getTechStackContext(root);
|
|
1271
1672
|
console.log('Execute your extraction plan from step 6.');
|
|
1272
1673
|
console.log();
|
|
1273
1674
|
console.log(chalk.bold('Components:'));
|
|
@@ -1280,19 +1681,45 @@ function printStep7(root, feature) {
|
|
|
1280
1681
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1281
1682
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1282
1683
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1684
|
+
console.log(chalk.dim(' EVERY conditional branch in the function MUST have a test that exercises it'));
|
|
1685
|
+
console.log(chalk.dim(' EVERY return path MUST be tested — if a function can return 3 different things, write 3+ tests'));
|
|
1686
|
+
console.log(chalk.dim(' Boundary values: 0, 1, -1, empty string, empty array, undefined, null'));
|
|
1283
1687
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1284
|
-
|
|
1688
|
+
console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
|
|
1689
|
+
if (ctx.isExpo) {
|
|
1690
|
+
checkbox('Place test files next to source but OUTSIDE `app/` (Expo Router treats all files in app/ as routes): `lib/storage.ts` → `lib/storage.test.ts`, `app/hooks/useCounter.ts` → `__tests__/hooks/useCounter.test.ts`');
|
|
1691
|
+
}
|
|
1692
|
+
else {
|
|
1693
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1694
|
+
}
|
|
1285
1695
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1286
1696
|
console.log();
|
|
1287
1697
|
console.log(chalk.bold('Recursive pass:'));
|
|
1288
1698
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1289
1699
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1290
|
-
console.log(chalk.yellow(
|
|
1700
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1291
1701
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1292
1702
|
console.log();
|
|
1703
|
+
console.log(chalk.bold('Decomposition pass (backend AND frontend):'));
|
|
1704
|
+
checkbox('Re-read EVERY file you created or modified. For each, extract a function/helper/sub-component for:');
|
|
1705
|
+
console.log(chalk.yellow(' — Conditionals (e.g. `isOverdue ? "red" : "green"` → `getStatusColor(date)`)'));
|
|
1706
|
+
console.log(chalk.yellow(' — Computed values (e.g. `items.filter(...).length` → `countActiveItems(items)`)'));
|
|
1707
|
+
console.log(chalk.yellow(' — Formatting (e.g. `${price.toFixed(2)}` → `formatPrice(price)`)'));
|
|
1708
|
+
console.log(chalk.yellow(' — Data transforms, validation, request/response shaping'));
|
|
1709
|
+
console.log(chalk.yellow(' — Anything doing more than one thing → split until each piece does one clearly defined thing'));
|
|
1710
|
+
checkbox('Then look at what you just extracted — can IT be broken down further? Keep going.');
|
|
1711
|
+
checkbox('Write tests for every extracted function (same TDD: failing tests FIRST)');
|
|
1712
|
+
console.log();
|
|
1713
|
+
console.log(chalk.bold.red('TEST QUALITY SELF-CHECK (before proceeding):'));
|
|
1714
|
+
checkbox('For EACH test file: count the test cases. Fewer than 3 tests for any function is a red flag.');
|
|
1715
|
+
checkbox('For EACH tested function: read the function, count the conditional branches. Every branch MUST have a test.');
|
|
1716
|
+
checkbox('For EACH tested function: verify you test at least one error/edge case, not just happy paths.');
|
|
1717
|
+
console.log(chalk.yellow(' If a function has an if/else, switch, or ternary — each path needs its own test case.'));
|
|
1718
|
+
console.log(chalk.yellow(' If a function handles null/undefined/empty — test those inputs explicitly.'));
|
|
1719
|
+
console.log();
|
|
1293
1720
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1294
1721
|
checkbox('Run all tests and verify they pass');
|
|
1295
|
-
checkbox(
|
|
1722
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1296
1723
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1297
1724
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1298
1725
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1300,6 +1727,8 @@ function printStep7(root, feature) {
|
|
|
1300
1727
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1301
1728
|
console.log();
|
|
1302
1729
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1730
|
+
console.log();
|
|
1731
|
+
checkboxHandoff();
|
|
1303
1732
|
stopGate(7);
|
|
1304
1733
|
}
|
|
1305
1734
|
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
@@ -1313,6 +1742,8 @@ function printStep8(root, feature) {
|
|
|
1313
1742
|
label: STEP_LABELS[8],
|
|
1314
1743
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1315
1744
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1745
|
+
appFormats: prevState?.appFormats,
|
|
1746
|
+
techStackId: prevState?.techStackId,
|
|
1316
1747
|
});
|
|
1317
1748
|
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1318
1749
|
stepHeader(8, 'Glossary', feature);
|
|
@@ -1339,6 +1770,8 @@ function printStep9(root, feature) {
|
|
|
1339
1770
|
label: STEP_LABELS[9],
|
|
1340
1771
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1341
1772
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1773
|
+
appFormats: prevState?.appFormats,
|
|
1774
|
+
techStackId: prevState?.techStackId,
|
|
1342
1775
|
});
|
|
1343
1776
|
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1344
1777
|
stepHeader(9, 'Analyze and Verify', feature);
|
|
@@ -1353,16 +1786,26 @@ function printStep9(root, feature) {
|
|
|
1353
1786
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1354
1787
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1355
1788
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1356
|
-
|
|
1789
|
+
const ctx9 = getTechStackContext(root);
|
|
1790
|
+
printComponentCaptureInstructions(root);
|
|
1357
1791
|
console.log();
|
|
1358
1792
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1359
1793
|
checkbox('Run ALL test files created in step 7');
|
|
1360
|
-
console.log(chalk.dim(
|
|
1794
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1361
1795
|
checkbox('Verify every test passes');
|
|
1362
1796
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1363
1797
|
console.log();
|
|
1364
1798
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1365
1799
|
console.log();
|
|
1800
|
+
console.log(chalk.bold.red('DECOMPOSITION & COVERAGE REVIEW (before running audit):'));
|
|
1801
|
+
checkbox('Re-read every file. Extract any logic that could be its own function/helper/sub-component.');
|
|
1802
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces.'));
|
|
1803
|
+
console.log(chalk.yellow(' Keep extracting until each piece does one clearly defined thing.'));
|
|
1804
|
+
console.log(chalk.yellow(' For each extraction: write failing test first, then extract, then make test pass.'));
|
|
1805
|
+
checkbox('Open each test file and verify: ≥3 test cases per function, happy path + edge case + error case');
|
|
1806
|
+
checkbox('If any function has fewer tests than conditional branches, add the missing tests NOW');
|
|
1807
|
+
checkbox('Update `.codeyam/glossary.json` with any new functions before running audit');
|
|
1808
|
+
console.log();
|
|
1366
1809
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1367
1810
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1368
1811
|
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
@@ -1388,6 +1831,8 @@ function printStep10(root, feature) {
|
|
|
1388
1831
|
label: STEP_LABELS[10],
|
|
1389
1832
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1390
1833
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1834
|
+
appFormats: prevState?.appFormats,
|
|
1835
|
+
techStackId: prevState?.techStackId,
|
|
1391
1836
|
});
|
|
1392
1837
|
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1393
1838
|
stepHeader(10, 'App Scenarios', feature);
|
|
@@ -1452,6 +1897,8 @@ function printStep11(root, feature) {
|
|
|
1452
1897
|
label: STEP_LABELS[11],
|
|
1453
1898
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1454
1899
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1900
|
+
appFormats: prevState?.appFormats,
|
|
1901
|
+
techStackId: prevState?.techStackId,
|
|
1455
1902
|
});
|
|
1456
1903
|
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1457
1904
|
stepHeader(11, 'User Scenarios', feature);
|
|
@@ -1498,6 +1945,8 @@ function printStep12(root, feature) {
|
|
|
1498
1945
|
label: STEP_LABELS[12],
|
|
1499
1946
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1500
1947
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1948
|
+
appFormats: prevState?.appFormats,
|
|
1949
|
+
techStackId: prevState?.techStackId,
|
|
1501
1950
|
});
|
|
1502
1951
|
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1503
1952
|
stepHeader(12, 'Verify', feature);
|
|
@@ -1544,6 +1993,8 @@ function printStep13(root, feature) {
|
|
|
1544
1993
|
label: STEP_LABELS[13],
|
|
1545
1994
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1546
1995
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1996
|
+
appFormats: prevState?.appFormats,
|
|
1997
|
+
techStackId: prevState?.techStackId,
|
|
1547
1998
|
});
|
|
1548
1999
|
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1549
2000
|
stepHeader(13, 'Journal', feature);
|
|
@@ -1577,6 +2028,8 @@ function printStep14(root, feature) {
|
|
|
1577
2028
|
label: STEP_LABELS[14],
|
|
1578
2029
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1579
2030
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2031
|
+
appFormats: prevState?.appFormats,
|
|
2032
|
+
techStackId: prevState?.techStackId,
|
|
1580
2033
|
});
|
|
1581
2034
|
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
1582
2035
|
stepHeader(14, 'Review', feature);
|
|
@@ -1596,6 +2049,8 @@ function printStep14(root, feature) {
|
|
|
1596
2049
|
checkbox('Fix or remove any broken images before continuing');
|
|
1597
2050
|
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1598
2051
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
2052
|
+
checkbox('Verify tech stack is up to date: check for any new service imports (e.g. Stripe, Resend, Supabase) and ensure they appear in the tech stack with `envKeys`');
|
|
2053
|
+
console.log(chalk.dim(` Update via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"techStack": {...}}'`));
|
|
1599
2054
|
checkbox('Do not proceed until all checks pass');
|
|
1600
2055
|
stopGate(14);
|
|
1601
2056
|
}
|
|
@@ -1610,6 +2065,8 @@ function printStep15(root, feature) {
|
|
|
1610
2065
|
label: STEP_LABELS[15],
|
|
1611
2066
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1612
2067
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2068
|
+
appFormats: prevState?.appFormats,
|
|
2069
|
+
techStackId: prevState?.techStackId,
|
|
1613
2070
|
});
|
|
1614
2071
|
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
1615
2072
|
stepHeader(15, 'Present', feature);
|
|
@@ -1838,7 +2295,7 @@ function printMigrateStep3(root) {
|
|
|
1838
2295
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1839
2296
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1840
2297
|
console.log();
|
|
1841
|
-
printComponentCaptureInstructions();
|
|
2298
|
+
printComponentCaptureInstructions(root);
|
|
1842
2299
|
console.log();
|
|
1843
2300
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1844
2301
|
}
|
|
@@ -1981,7 +2438,7 @@ function printMigrateStep8(root) {
|
|
|
1981
2438
|
console.log();
|
|
1982
2439
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1983
2440
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1984
|
-
printComponentCaptureInstructions();
|
|
2441
|
+
printComponentCaptureInstructions(root);
|
|
1985
2442
|
console.log();
|
|
1986
2443
|
console.log(chalk.bold('Verify:'));
|
|
1987
2444
|
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
@@ -2259,6 +2716,8 @@ function printStep16(root, feature) {
|
|
|
2259
2716
|
label: STEP_LABELS[16],
|
|
2260
2717
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2261
2718
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2719
|
+
appFormats: prevState?.appFormats,
|
|
2720
|
+
techStackId: prevState?.techStackId,
|
|
2262
2721
|
});
|
|
2263
2722
|
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2264
2723
|
stepHeader(16, 'Commit', feature);
|
|
@@ -2285,6 +2744,8 @@ function printStep17(root, feature) {
|
|
|
2285
2744
|
label: STEP_LABELS[17],
|
|
2286
2745
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2287
2746
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2747
|
+
appFormats: prevState?.appFormats,
|
|
2748
|
+
techStackId: prevState?.techStackId,
|
|
2288
2749
|
});
|
|
2289
2750
|
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2290
2751
|
stepHeader(17, 'Finalize', feature);
|
|
@@ -2310,6 +2771,8 @@ function printStep18(root, feature) {
|
|
|
2310
2771
|
label: STEP_LABELS[18],
|
|
2311
2772
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2312
2773
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2774
|
+
appFormats: prevState?.appFormats,
|
|
2775
|
+
techStackId: prevState?.techStackId,
|
|
2313
2776
|
});
|
|
2314
2777
|
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2315
2778
|
stepHeader(18, 'Push', feature);
|
|
@@ -2327,11 +2790,11 @@ function printStep18(root, feature) {
|
|
|
2327
2790
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2328
2791
|
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2329
2792
|
console.log();
|
|
2330
|
-
|
|
2331
|
-
console.log(chalk.yellow('
|
|
2793
|
+
const port = getServerPort();
|
|
2794
|
+
console.log(chalk.bold.yellow('IMPORTANT: After the push succeeds (or the user skips it), the feature is DONE.'));
|
|
2795
|
+
console.log(chalk.yellow(` Signal the UI by running: curl -s -X POST http://localhost:${port}/api/editor-feature-complete`));
|
|
2796
|
+
console.log(chalk.yellow(' Then STOP and wait. Do NOT ask the user what to build next — the UI handles the transition.'));
|
|
2332
2797
|
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2333
|
-
console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
|
|
2334
|
-
console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
|
|
2335
2798
|
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2336
2799
|
stopGate(18, { confirm: true });
|
|
2337
2800
|
}
|
|
@@ -2744,41 +3207,78 @@ function handleDesignSystem(designSystemId) {
|
|
|
2744
3207
|
}
|
|
2745
3208
|
function handleIsolate(componentNames) {
|
|
2746
3209
|
const root = process.cwd();
|
|
3210
|
+
const ctx = getTechStackContext(root);
|
|
2747
3211
|
if (componentNames.length === 0) {
|
|
2748
3212
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2749
3213
|
process.exit(1);
|
|
2750
3214
|
}
|
|
2751
3215
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2752
|
-
// Create
|
|
2753
|
-
|
|
3216
|
+
// Create the framework-appropriate layout guard if missing.
|
|
3217
|
+
// Clean up wrong-framework layout from a previous CLI version.
|
|
3218
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
3219
|
+
const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
|
|
3220
|
+
if (fs.existsSync(wrongLayoutPath)) {
|
|
3221
|
+
fs.unlinkSync(wrongLayoutPath);
|
|
3222
|
+
}
|
|
2754
3223
|
if (!fs.existsSync(layoutPath)) {
|
|
2755
3224
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
3225
|
+
if (ctx.isExpo) {
|
|
3226
|
+
fs.writeFileSync(layoutPath, [
|
|
3227
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
3228
|
+
'',
|
|
3229
|
+
'export default function CaptureLayout() {',
|
|
3230
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
3231
|
+
' return <Slot />;',
|
|
3232
|
+
'}',
|
|
3233
|
+
'',
|
|
3234
|
+
].join('\n'), 'utf8');
|
|
3235
|
+
}
|
|
3236
|
+
else {
|
|
3237
|
+
fs.writeFileSync(layoutPath, [
|
|
3238
|
+
'import { notFound } from "next/navigation";',
|
|
3239
|
+
'',
|
|
3240
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
3241
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
3242
|
+
' return <>{children}</>;',
|
|
3243
|
+
'}',
|
|
3244
|
+
'',
|
|
3245
|
+
].join('\n'), 'utf8');
|
|
3246
|
+
}
|
|
3247
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
3248
|
+
}
|
|
3249
|
+
// Create isolation route for each component.
|
|
3250
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2768
3251
|
const created = [];
|
|
2769
3252
|
const existed = [];
|
|
2770
3253
|
for (const name of componentNames) {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3254
|
+
if (ctx.isExpo) {
|
|
3255
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
3256
|
+
if (fs.existsSync(filePath)) {
|
|
3257
|
+
existed.push(name);
|
|
3258
|
+
}
|
|
3259
|
+
else {
|
|
3260
|
+
created.push(name);
|
|
3261
|
+
}
|
|
2774
3262
|
}
|
|
2775
3263
|
else {
|
|
2776
|
-
|
|
2777
|
-
|
|
3264
|
+
const dir = path.join(isolateDir, name);
|
|
3265
|
+
if (fs.existsSync(dir)) {
|
|
3266
|
+
existed.push(name);
|
|
3267
|
+
}
|
|
3268
|
+
else {
|
|
3269
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3270
|
+
created.push(name);
|
|
3271
|
+
}
|
|
2778
3272
|
}
|
|
2779
3273
|
}
|
|
2780
3274
|
if (created.length > 0) {
|
|
2781
|
-
|
|
3275
|
+
if (ctx.isExpo) {
|
|
3276
|
+
console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
|
|
3277
|
+
console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
|
|
3278
|
+
}
|
|
3279
|
+
else {
|
|
3280
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
3281
|
+
}
|
|
2782
3282
|
}
|
|
2783
3283
|
if (existed.length > 0) {
|
|
2784
3284
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2832,6 +3332,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2832
3332
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2833
3333
|
if (data.sessionsNotified != null)
|
|
2834
3334
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
3335
|
+
// Surface the HTTP health check from the dev server
|
|
3336
|
+
if (data.preview) {
|
|
3337
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
3338
|
+
if (data.preview.error)
|
|
3339
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3340
|
+
}
|
|
2835
3341
|
return parts.join(' ');
|
|
2836
3342
|
}
|
|
2837
3343
|
case 'dev-server': {
|
|
@@ -2913,6 +3419,50 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2913
3419
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2914
3420
|
}
|
|
2915
3421
|
}
|
|
3422
|
+
/**
|
|
3423
|
+
* `codeyam editor handoff '{"summary":"..."}'`
|
|
3424
|
+
*
|
|
3425
|
+
* Update the handoff summary in .codeyam/config.json for other AI providers.
|
|
3426
|
+
*/
|
|
3427
|
+
function handleHandoff(jsonArg) {
|
|
3428
|
+
if (!jsonArg) {
|
|
3429
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3430
|
+
console.error(chalk.dim(' Usage: codeyam editor handoff \'{"summary":"Built the X component..."}\''));
|
|
3431
|
+
process.exit(1);
|
|
3432
|
+
}
|
|
3433
|
+
let summary;
|
|
3434
|
+
try {
|
|
3435
|
+
const parsed = JSON.parse(jsonArg);
|
|
3436
|
+
summary = parsed.summary;
|
|
3437
|
+
}
|
|
3438
|
+
catch {
|
|
3439
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
3440
|
+
process.exit(1);
|
|
3441
|
+
}
|
|
3442
|
+
if (!summary) {
|
|
3443
|
+
console.error(chalk.red('Error: "summary" field is required.'));
|
|
3444
|
+
process.exit(1);
|
|
3445
|
+
}
|
|
3446
|
+
const root = getProjectRoot();
|
|
3447
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3448
|
+
try {
|
|
3449
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3450
|
+
const state = readState(root);
|
|
3451
|
+
const provider = config.provider || 'claude';
|
|
3452
|
+
config.handoff = {
|
|
3453
|
+
summary,
|
|
3454
|
+
lastProvider: provider,
|
|
3455
|
+
lastStep: state?.step,
|
|
3456
|
+
lastUpdated: new Date().toISOString(),
|
|
3457
|
+
};
|
|
3458
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
3459
|
+
console.log(chalk.green('✓ Handoff summary updated in .codeyam/config.json'));
|
|
3460
|
+
}
|
|
3461
|
+
catch (err) {
|
|
3462
|
+
console.error(chalk.red(`Error: Could not update config.json: ${err.message}`));
|
|
3463
|
+
process.exit(1);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
2916
3466
|
/**
|
|
2917
3467
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
2918
3468
|
*
|
|
@@ -3781,14 +4331,20 @@ async function handleAudit(options) {
|
|
|
3781
4331
|
let detail;
|
|
3782
4332
|
switch (f.status) {
|
|
3783
4333
|
case 'ok':
|
|
3784
|
-
|
|
4334
|
+
if (f.testCaseCount !== undefined && f.testCaseCount < 3) {
|
|
4335
|
+
detail = chalk.yellow(` (${f.testFile}) — ⚠ only ${f.testCaseCount} test case${f.testCaseCount !== 1 ? 's' : ''}, consider adding more`);
|
|
4336
|
+
}
|
|
4337
|
+
else {
|
|
4338
|
+
detail = chalk.dim(` (${f.testFile}${f.testCaseCount !== undefined ? `, ${f.testCaseCount} tests` : ''})`);
|
|
4339
|
+
}
|
|
3785
4340
|
break;
|
|
3786
4341
|
case 'runner_error':
|
|
3787
4342
|
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3788
4343
|
if (f.errorMessage) {
|
|
3789
4344
|
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3790
4345
|
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3791
|
-
|
|
4346
|
+
const ctx = getTechStackContext(process.cwd());
|
|
4347
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
3792
4348
|
}
|
|
3793
4349
|
break;
|
|
3794
4350
|
case 'failing':
|
|
@@ -3813,6 +4369,9 @@ async function handleAudit(options) {
|
|
|
3813
4369
|
}
|
|
3814
4370
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
3815
4371
|
}
|
|
4372
|
+
if (summary.functionsThinCoverage > 0) {
|
|
4373
|
+
console.log(chalk.yellow.bold(` ⚠ ${summary.functionsThinCoverage} function${summary.functionsThinCoverage !== 1 ? 's' : ''} with thin test coverage (< 3 test cases). Add more tests to cover all branches.`));
|
|
4374
|
+
}
|
|
3816
4375
|
console.log();
|
|
3817
4376
|
}
|
|
3818
4377
|
// Missing from glossary
|
|
@@ -4371,7 +4930,7 @@ async function handleTemplate() {
|
|
|
4371
4930
|
_: [],
|
|
4372
4931
|
});
|
|
4373
4932
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
4374
|
-
// 5. Verify config has startCommand
|
|
4933
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
4375
4934
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4376
4935
|
if (fs.existsSync(configPath)) {
|
|
4377
4936
|
try {
|
|
@@ -4382,6 +4941,30 @@ async function handleTemplate() {
|
|
|
4382
4941
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
4383
4942
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
4384
4943
|
}
|
|
4944
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4945
|
+
if (stack?.supportedFormats) {
|
|
4946
|
+
config.appFormats = stack.supportedFormats;
|
|
4947
|
+
}
|
|
4948
|
+
// Pre-populate tech stack from template
|
|
4949
|
+
if (stack) {
|
|
4950
|
+
config.techStack = getTechStackForTemplate(stack.id);
|
|
4951
|
+
}
|
|
4952
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4953
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4954
|
+
config.defaultScreenSize = {
|
|
4955
|
+
name: 'iPhone 16',
|
|
4956
|
+
width: 393,
|
|
4957
|
+
height: 852,
|
|
4958
|
+
};
|
|
4959
|
+
config.screenSizes = {
|
|
4960
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4961
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4962
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4963
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4964
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4967
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4385
4968
|
}
|
|
4386
4969
|
catch {
|
|
4387
4970
|
// Config parse error is non-fatal
|
|
@@ -4416,7 +4999,15 @@ async function handleTemplate() {
|
|
|
4416
4999
|
}
|
|
4417
5000
|
console.log();
|
|
4418
5001
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
4419
|
-
|
|
5002
|
+
if (stack?.id === 'expo-react-native') {
|
|
5003
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
5004
|
+
}
|
|
5005
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
5006
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
5007
|
+
}
|
|
5008
|
+
else {
|
|
5009
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
5010
|
+
}
|
|
4420
5011
|
}
|
|
4421
5012
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
4422
5013
|
/**
|
|
@@ -5030,6 +5621,11 @@ const editorCommand = {
|
|
|
5030
5621
|
}
|
|
5031
5622
|
return;
|
|
5032
5623
|
}
|
|
5624
|
+
// Subcommand: codeyam editor handoff '{"summary":"..."}'
|
|
5625
|
+
if (argv.step === 'handoff') {
|
|
5626
|
+
await handleHandoff(argv.json || '');
|
|
5627
|
+
return;
|
|
5628
|
+
}
|
|
5033
5629
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
5034
5630
|
if (argv.step === 'register') {
|
|
5035
5631
|
await handleRegister(argv.json || '');
|
|
@@ -5212,10 +5808,18 @@ const editorCommand = {
|
|
|
5212
5808
|
try {
|
|
5213
5809
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
5214
5810
|
const { projectSlug } = config;
|
|
5811
|
+
// Detect provider switch and inform the user
|
|
5812
|
+
const handoff = config.handoff;
|
|
5813
|
+
const currentProvider = config.provider || 'claude';
|
|
5814
|
+
if (handoff?.lastProvider && handoff.lastProvider !== currentProvider) {
|
|
5815
|
+
console.log(chalk.yellow(` Provider changed: ${handoff.lastProvider} → ${currentProvider}`));
|
|
5816
|
+
console.log(chalk.dim(' The editor will offer to continue with handoff context.'));
|
|
5817
|
+
}
|
|
5215
5818
|
if (!projectSlug) {
|
|
5216
5819
|
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
5217
5820
|
return;
|
|
5218
5821
|
}
|
|
5822
|
+
console.log(chalk.dim(` CodeYam Editor v${getDisplayVersion()}`));
|
|
5219
5823
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
5220
5824
|
if (!connectionOk) {
|
|
5221
5825
|
errorLog('Environment validation failed');
|
|
@@ -5627,6 +6231,9 @@ const editorCommand = {
|
|
|
5627
6231
|
process.exit(1);
|
|
5628
6232
|
}
|
|
5629
6233
|
}
|
|
6234
|
+
printHandoffContext(root);
|
|
6235
|
+
// Track step progress automatically for provider handoff
|
|
6236
|
+
updateHandoffProgress(root, step);
|
|
5630
6237
|
switch (step) {
|
|
5631
6238
|
case 1: {
|
|
5632
6239
|
const feature = argv.feature || undefined;
|