@codeyam/codeyam-cli 0.1.0-staging.2ea44f6 → 0.1.0-staging.3ef993e
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 +8 -8
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +1 -1
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +73 -0
- package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
- package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +6 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +73 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts +2 -0
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js +2 -0
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js.map +1 -1
- package/analyzer-template/packages/types/src/enums/ProjectFramework.ts +2 -0
- package/analyzer-template/packages/ui-components/package.json +1 -1
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts +2 -0
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js +2 -0
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +45 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +1265 -213
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +67 -34
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/data/techStacks.js +77 -0
- package/codeyam-cli/src/data/techStacks.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js +46 -0
- package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +508 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js +152 -3
- package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +140 -12
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +238 -2
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +128 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +191 -5
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +153 -0
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js +139 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +221 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +887 -2
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +213 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +289 -40
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +107 -0
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +129 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +227 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +426 -218
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +13 -5
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js +51 -0
- package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +142 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -0
- package/codeyam-cli/src/utils/analyzer.js +9 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
- package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
- package/codeyam-cli/src/utils/backgroundServer.js +92 -16
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/database.js +37 -2
- package/codeyam-cli/src/utils/database.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +91 -3
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorDevServer.js +89 -1
- package/codeyam-cli/src/utils/editorDevServer.js.map +1 -1
- package/codeyam-cli/src/utils/editorImageVerifier.js +45 -10
- package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -1
- package/codeyam-cli/src/utils/editorJournal.js +78 -3
- package/codeyam-cli/src/utils/editorJournal.js.map +1 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +32 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +43 -2
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +112 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarios.js +291 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +173 -0
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -0
- package/codeyam-cli/src/utils/entityChangeStatus.js +42 -19
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +56 -5
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/parseRegisterArg.js +31 -0
- package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -0
- package/codeyam-cli/src/utils/progress.js +2 -2
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js +75 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +204 -75
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/serverState.js +30 -0
- package/codeyam-cli/src/utils/serverState.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +13 -5
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/slugUtils.js +25 -0
- package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
- package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
- package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/webappDetection.js +21 -0
- package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +40 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +567 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +65 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
- package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-DmJveP3T.js → CopyButton-BPXZwM4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-C76mRRiF.js → EntityItem-BcgbViKV.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CobE682z.js → EntityTypeIcon-CQIG2qda.js} +9 -9
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-djPLI-WV.js → ReportIssueModal-BzHcG7SE.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-ZlRKbhrq.js → ScenarioViewer-TSD3C211.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-C96V0n15.js → _index-DLxKhri3.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BpKzcsJz.js → activity.(_tab)-BcY3q6nt.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-D9hemwl6.js → agent-transcripts-Bni3iiUj.js} +5 -5
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-D_nMCFmP.js → book-open-BYOypzCa.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BH2h1Ea2.js → chevron-down-C_Pmso5S.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DyIKORY6.js → circle-check-BVMi9VA5.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{copy-NDbZjXao.js → copy-n2FB0_Sw.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CC6AbExI.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CltMNppm.js → dev.empty-Ii3inc0_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-COWCNVyV.js +10 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CNB06EIa.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DItJnD8s.js → entity._sha._-DwCV5__E.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-CCa2trIL.js → entity._sha.scenarios._scenarioId.dev-CXSi2aeZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/{files-DO4CZ16O.js → files-BZrlFE1F.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-CdN8sCqs.js → git-DdZcvjGh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-phvmGvat.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-BAXYRVEO.js → loader-circle-DaAZ_H2w.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/manifest-6134dc40.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/memory-9gnxSZlb.js +101 -0
- package/codeyam-cli/src/webserver/build/client/assets/{pause-DTAcYxBt.js → pause-f5-1lKBt.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/root-BWAyuj0r.js +67 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-fKo7v0Zo.js → search-Di64LWVb.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{settings-DfuTtcJP.js → settings-0OrEMU6J.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-B3aOzpCZ.js → simulations-DWT-CvLy.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-BG4heKCG.js → terminal-Br7MOqts.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DtSmdtM4.js → triangle-alert-BLdiCuG-.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +1 -0
- package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/build/server/assets/index-ChX0hPcu.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/init-kSNsMjj8.js +10 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Bm2xIhmh.js +439 -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 +487 -17
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +73 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
- package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/scripts/codeyam-preload.mjs +21 -3
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +94 -4
- package/codeyam-cli/src/webserver/server.js +59 -34
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +64 -32
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/chrome-extension-react/EXTENSION_SETUP.md +75 -0
- package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
- package/codeyam-cli/templates/chrome-extension-react/gitignore +15 -0
- package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +27 -0
- package/codeyam-cli/templates/chrome-extension-react/popup.html +12 -0
- package/codeyam-cli/templates/chrome-extension-react/public/manifest.json +15 -0
- package/codeyam-cli/templates/chrome-extension-react/src/background/service-worker.ts +7 -0
- package/codeyam-cli/templates/chrome-extension-react/src/globals.css +6 -0
- package/codeyam-cli/templates/chrome-extension-react/src/lib/storage.ts +37 -0
- package/codeyam-cli/templates/chrome-extension-react/src/popup/App.tsx +12 -0
- package/codeyam-cli/templates/chrome-extension-react/src/popup/main.tsx +10 -0
- package/codeyam-cli/templates/chrome-extension-react/tsconfig.json +24 -0
- package/codeyam-cli/templates/chrome-extension-react/vite.config.ts +41 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
- package/codeyam-cli/templates/editor-step-hook.py +37 -8
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -0
- package/codeyam-cli/templates/expo-react-native/README.md +41 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +33 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app.json +18 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +9 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +12 -0
- package/codeyam-cli/templates/expo-react-native/global.css +3 -0
- package/codeyam-cli/templates/expo-react-native/lib/storage.ts +32 -0
- package/codeyam-cli/templates/expo-react-native/metro.config.js +6 -0
- package/codeyam-cli/templates/expo-react-native/nativewind-env.d.ts +1 -0
- package/codeyam-cli/templates/expo-react-native/package.json +38 -0
- package/codeyam-cli/templates/expo-react-native/tailwind.config.js +10 -0
- package/codeyam-cli/templates/expo-react-native/tsconfig.json +10 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_PATTERNS.md +308 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_UPGRADE.md +304 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +126 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/app/lib/prisma.ts +9 -4
- package/codeyam-cli/templates/nextjs-prisma-sqlite/env +4 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/seed.ts +4 -1
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +92 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
- package/codeyam-cli/templates/{nextjs-prisma-sqlite/PRISMA_SETUP.md → nextjs-prisma-supabase/SUPABASE_SETUP.md} +37 -17
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/api/todos/route.ts +17 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/globals.css +26 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/layout.tsx +34 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/prisma.ts +20 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/supabase.ts +12 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/page.tsx +10 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/env +9 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/eslint.config.mjs +11 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/gitignore +40 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/next.config.ts +11 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +37 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/postcss.config.mjs +7 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/schema.prisma +27 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/seed.ts +39 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma.config.ts +12 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/tsconfig.json +34 -0
- package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +28 -9
- package/package.json +1 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +73 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/database/src/lib/loadEntities.js +0 -6
- package/packages/database/src/lib/loadEntities.js.map +1 -1
- package/packages/database/src/lib/updateCommitMetadata.js +0 -25
- package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/packages/types/src/enums/ProjectFramework.js +2 -0
- package/packages/types/src/enums/ProjectFramework.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CMT1jU2q.js +0 -21
- package/codeyam-cli/src/webserver/build/client/assets/editor-Rfq_y0VR.js +0 -10
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-GNwaLSmC.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/globals-Bd0cs8vw.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-9ab0aba3.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-Dg0mvYrI.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/root-3ciuWk-c.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/index-DCxIbVvl.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-E-peu3XZ.js +0 -367
|
@@ -11,12 +11,15 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
|
|
|
11
11
|
import { startBackgroundServer } from "../utils/backgroundServer.js";
|
|
12
12
|
import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
|
-
import {
|
|
14
|
+
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
|
+
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
15
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
16
17
|
import initCommand from "./init.js";
|
|
17
|
-
import {
|
|
18
|
-
import { clearEditorState, clearEditorUserPrompt, } from "../utils/editorScenarios.js";
|
|
18
|
+
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
19
21
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
|
+
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
20
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
24
|
const __dirname = path.dirname(__filename);
|
|
22
25
|
const STEP_LABELS = {
|
|
@@ -33,6 +36,9 @@ const STEP_LABELS = {
|
|
|
33
36
|
11: 'Journal',
|
|
34
37
|
12: 'Review',
|
|
35
38
|
13: 'Present',
|
|
39
|
+
14: 'Commit',
|
|
40
|
+
15: 'Finalize',
|
|
41
|
+
16: 'Push',
|
|
36
42
|
};
|
|
37
43
|
/**
|
|
38
44
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
@@ -53,6 +59,18 @@ function logEvent(root, event, data) {
|
|
|
53
59
|
// Logging is best-effort
|
|
54
60
|
}
|
|
55
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Read the design system file if it exists.
|
|
64
|
+
*/
|
|
65
|
+
function readDesignSystem(root) {
|
|
66
|
+
const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
|
|
67
|
+
try {
|
|
68
|
+
return fs.readFileSync(designSystemPath, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
56
74
|
/**
|
|
57
75
|
* Get the project root (where .codeyam/ lives) or cwd.
|
|
58
76
|
*/
|
|
@@ -102,11 +120,62 @@ function hasProject(root) {
|
|
|
102
120
|
return fs.existsSync(path.join(root, 'package.json'));
|
|
103
121
|
}
|
|
104
122
|
/**
|
|
105
|
-
* Get the CodeYam server port
|
|
123
|
+
* Get the CodeYam server port.
|
|
124
|
+
* Reads from the server state file first (actual running port), then falls
|
|
125
|
+
* back to CODEYAM_PORT env var, then to the default 3111.
|
|
126
|
+
* This is critical when the server auto-selected a different port because
|
|
127
|
+
* the default was occupied by another project's server.
|
|
106
128
|
*/
|
|
107
129
|
function getServerPort() {
|
|
130
|
+
try {
|
|
131
|
+
const root = getProjectRoot();
|
|
132
|
+
if (root) {
|
|
133
|
+
const statePath = path.join(root, '.codeyam', 'server.json');
|
|
134
|
+
if (fs.existsSync(statePath)) {
|
|
135
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
136
|
+
if (state.port)
|
|
137
|
+
return String(state.port);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Fall through to defaults
|
|
143
|
+
}
|
|
108
144
|
return process.env.CODEYAM_PORT || '3111';
|
|
109
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Read the project's default dimension name and available screen size names.
|
|
148
|
+
* Used by step instructions to show project-specific dimension examples
|
|
149
|
+
* instead of hardcoded "Desktop".
|
|
150
|
+
*/
|
|
151
|
+
function getProjectDimensions(root) {
|
|
152
|
+
try {
|
|
153
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
154
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
155
|
+
const defaultName = config.defaultScreenSize?.name ||
|
|
156
|
+
(config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
|
|
157
|
+
'Desktop';
|
|
158
|
+
const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
|
|
159
|
+
return { defaultName, names };
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return { defaultName: 'Desktop', names: [] };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Print dimension guidance when the project has multiple screen sizes.
|
|
167
|
+
* Tells Claude to pick the right dimension for the content being previewed
|
|
168
|
+
* instead of blindly using the default for everything.
|
|
169
|
+
*/
|
|
170
|
+
function printDimensionGuidance(defaultName, names) {
|
|
171
|
+
if (names.length <= 1)
|
|
172
|
+
return;
|
|
173
|
+
// Find a non-default dimension to suggest as the "other" option
|
|
174
|
+
const otherName = names.find((n) => n !== defaultName) || names[1];
|
|
175
|
+
console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
|
|
176
|
+
console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
|
|
177
|
+
console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
|
|
178
|
+
}
|
|
110
179
|
/**
|
|
111
180
|
* Print a checklist item.
|
|
112
181
|
* Inline backtick-wrapped text is highlighted in cyan for visibility.
|
|
@@ -128,13 +197,13 @@ function stepHeader(step, title, feature) {
|
|
|
128
197
|
console.log();
|
|
129
198
|
}
|
|
130
199
|
/**
|
|
131
|
-
* Print a colored progress tracker showing all
|
|
200
|
+
* Print a colored progress tracker showing all 16 steps.
|
|
132
201
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
133
202
|
*/
|
|
134
203
|
function printProgressTracker(current) {
|
|
135
204
|
console.log();
|
|
136
|
-
console.log(chalk.dim('┌─────────────────────────────────────┐'));
|
|
137
|
-
for (let i = 1; i <=
|
|
205
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
206
|
+
for (let i = 1; i <= 16; i++) {
|
|
138
207
|
const label = STEP_LABELS[i];
|
|
139
208
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
140
209
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -172,7 +241,7 @@ function stopGate(current, opts) {
|
|
|
172
241
|
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
173
242
|
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
174
243
|
console.log();
|
|
175
|
-
if (current <
|
|
244
|
+
if (current < 16) {
|
|
176
245
|
console.log(chalk.green('When done, run: ') +
|
|
177
246
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
178
247
|
}
|
|
@@ -216,6 +285,14 @@ function printResumptionHeader(step) {
|
|
|
216
285
|
],
|
|
217
286
|
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
218
287
|
13: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
288
|
+
14: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
289
|
+
15: [
|
|
290
|
+
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
291
|
+
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
292
|
+
],
|
|
293
|
+
16: [
|
|
294
|
+
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
295
|
+
],
|
|
219
296
|
};
|
|
220
297
|
const label = STEP_LABELS[step] || 'Unknown';
|
|
221
298
|
console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
|
|
@@ -320,7 +397,7 @@ function parseDebugTargets(target) {
|
|
|
320
397
|
}
|
|
321
398
|
if (entry.startsWith('step-')) {
|
|
322
399
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
323
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
400
|
+
if (!isNaN(num) && num >= 1 && num <= 16) {
|
|
324
401
|
valid.add(`step-${num}`);
|
|
325
402
|
continue;
|
|
326
403
|
}
|
|
@@ -404,17 +481,88 @@ function printSetup(root) {
|
|
|
404
481
|
console.log();
|
|
405
482
|
console.log("No project detected. Let's get started.");
|
|
406
483
|
console.log();
|
|
484
|
+
// ── Design System ────────────────────────────────────────────────
|
|
485
|
+
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
486
|
+
console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
|
|
487
|
+
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
488
|
+
console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
|
|
489
|
+
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
490
|
+
console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
|
|
491
|
+
console.log(chalk.dim(' → Skip'));
|
|
492
|
+
console.log();
|
|
407
493
|
console.log(chalk.bold('Checklist:'));
|
|
408
494
|
checkbox('Read `.codeyam/editor-mode-context.md` for session state');
|
|
409
495
|
checkbox('Ask the user what they want to build');
|
|
410
496
|
console.log();
|
|
497
|
+
// ── App Format Selection ───────────────────────────────────────────
|
|
498
|
+
console.log(chalk.bold('App Format Selection:'));
|
|
499
|
+
console.log(chalk.dim(' After the user describes their project, ask which app formats they want to target.'));
|
|
500
|
+
console.log(chalk.dim(' Use AskUserQuestion with CHECKBOXES (multiple selections allowed):'));
|
|
501
|
+
console.log();
|
|
502
|
+
for (const format of APP_FORMATS) {
|
|
503
|
+
console.log(chalk.yellow(` [ ] ${format.label}`) +
|
|
504
|
+
chalk.dim(` — ${format.description}`));
|
|
505
|
+
}
|
|
506
|
+
console.log();
|
|
507
|
+
// ── Tech Stack Selection ───────────────────────────────────────────
|
|
508
|
+
console.log(chalk.bold('Tech Stack Selection:'));
|
|
509
|
+
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
510
|
+
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
511
|
+
console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
|
|
512
|
+
console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
|
|
513
|
+
console.log();
|
|
514
|
+
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
515
|
+
for (const stack of TECH_STACKS) {
|
|
516
|
+
const recommended = stack.recommended ? chalk.green(' (Recommended)') : '';
|
|
517
|
+
const formatLabels = stack.supportedFormats
|
|
518
|
+
.map((f) => APP_FORMATS.find((af) => af.id === f)?.label)
|
|
519
|
+
.join(', ');
|
|
520
|
+
console.log(chalk.bold.yellow(` ${stack.name}`) +
|
|
521
|
+
recommended +
|
|
522
|
+
chalk.dim(` [id: ${stack.id}]`));
|
|
523
|
+
console.log(chalk.dim(` ${stack.description}`));
|
|
524
|
+
console.log(chalk.dim(' Technologies:'));
|
|
525
|
+
for (const tech of stack.technologies) {
|
|
526
|
+
console.log(chalk.dim(` - ${tech}`));
|
|
527
|
+
}
|
|
528
|
+
console.log(chalk.dim(` Supports: ${formatLabels}`));
|
|
529
|
+
console.log();
|
|
530
|
+
}
|
|
531
|
+
console.log(chalk.dim(' If NO stacks match the selected formats, tell the user that support for that format is coming soon'));
|
|
532
|
+
console.log(chalk.dim(' and suggest they pick a supported format for now.'));
|
|
533
|
+
console.log();
|
|
534
|
+
// ── Default Screen Size Selection ──────────────────────────────────
|
|
535
|
+
console.log(chalk.bold('Default Screen Size:'));
|
|
536
|
+
console.log(chalk.dim(' After selecting a tech stack, ask the user to choose the default screen size for previews and captures.'));
|
|
537
|
+
console.log(chalk.dim(' Use AskUserQuestion with RADIO buttons (single selection):'));
|
|
538
|
+
console.log();
|
|
539
|
+
console.log(chalk.yellow(' ( ) Desktop') + chalk.dim(' — 1440 × 900'));
|
|
540
|
+
console.log(chalk.yellow(' ( ) Laptop') + chalk.dim(' — 1024 × 768'));
|
|
541
|
+
console.log(chalk.yellow(' ( ) Tablet') + chalk.dim(' — 768 × 1024'));
|
|
542
|
+
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
543
|
+
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
544
|
+
console.log();
|
|
545
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
|
|
546
|
+
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
547
|
+
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}}'`));
|
|
548
|
+
console.log();
|
|
549
|
+
console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
|
|
550
|
+
console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
|
|
551
|
+
console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
|
|
552
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
553
|
+
console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
|
|
554
|
+
console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
|
|
555
|
+
console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
|
|
556
|
+
console.log();
|
|
411
557
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
412
558
|
console.log();
|
|
413
|
-
console.log(chalk.red('Ask the user what they want to build, then run:'));
|
|
559
|
+
console.log(chalk.red('Ask the user what they want to build, then ask about app formats, then present tech stacks, then choose a default screen size. Then run:'));
|
|
414
560
|
console.log();
|
|
415
561
|
console.log(chalk.green(' ') +
|
|
416
|
-
chalk.bold('codeyam editor 1 --feature "Feature Name" --prompt "the user\'s original message"'));
|
|
562
|
+
chalk.bold('codeyam editor 1 --feature "Feature Name" --app-formats "FORMAT_IDS" --tech-stack "STACK_ID" --prompt "the user\'s original message"'));
|
|
417
563
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what the user described.'));
|
|
564
|
+
console.log(chalk.dim(' Replace FORMAT_IDS with the comma-separated format IDs the user selected (e.g., "chrome-extension" or "mobile-responsive-web-app").'));
|
|
565
|
+
console.log(chalk.dim(' Replace STACK_ID with the tech stack id shown in [brackets] above (e.g., "nextjs-prisma-sqlite" or "chrome-extension-react").'));
|
|
418
566
|
console.log(chalk.dim(" Pass --prompt with the user's exact original request so it appears in the journal."));
|
|
419
567
|
console.log(chalk.dim(' Step 1 will guide you through planning and getting user confirmation before any code is written.'));
|
|
420
568
|
console.log();
|
|
@@ -433,9 +581,13 @@ function printCycleOverview(root, state) {
|
|
|
433
581
|
console.log(chalk.dim('Or run ') +
|
|
434
582
|
chalk.bold('codeyam editor 1') +
|
|
435
583
|
chalk.dim(' to start a new feature'));
|
|
584
|
+
console.log();
|
|
585
|
+
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
586
|
+
chalk.bold('codeyam editor change'));
|
|
587
|
+
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
436
588
|
}
|
|
437
589
|
else {
|
|
438
|
-
console.log('Each feature follows
|
|
590
|
+
console.log('Each feature follows 16 steps. You MUST run each command in order:');
|
|
439
591
|
console.log();
|
|
440
592
|
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
441
593
|
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
@@ -450,6 +602,9 @@ function printCycleOverview(root, state) {
|
|
|
450
602
|
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
451
603
|
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
452
604
|
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
605
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
606
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
607
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
|
|
453
608
|
console.log();
|
|
454
609
|
console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
|
|
455
610
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
@@ -457,23 +612,26 @@ function printCycleOverview(root, state) {
|
|
|
457
612
|
console.log();
|
|
458
613
|
}
|
|
459
614
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
460
|
-
function printStep1(root, feature, userPrompt) {
|
|
615
|
+
function printStep1(root, feature, options, userPrompt) {
|
|
616
|
+
const port = getServerPort();
|
|
461
617
|
const prevState = readState(root);
|
|
462
618
|
const isResuming = prevState?.step === 1;
|
|
463
619
|
if (!isResuming) {
|
|
464
620
|
clearState(root);
|
|
465
621
|
}
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
622
|
+
// Always persist state so step 2's validation sees that step 1 ran.
|
|
623
|
+
// The feature name may not be known yet (it's an output of planning) —
|
|
624
|
+
// use an empty string as placeholder; step 2 requires --feature anyway.
|
|
625
|
+
const now = new Date().toISOString();
|
|
626
|
+
writeState(root, {
|
|
627
|
+
feature: feature || prevState?.feature || '',
|
|
628
|
+
step: 1,
|
|
629
|
+
label: STEP_LABELS[1],
|
|
630
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
631
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
632
|
+
appFormats: options?.appFormats || prevState?.appFormats,
|
|
633
|
+
techStackId: options?.techStackId || prevState?.techStackId,
|
|
634
|
+
});
|
|
477
635
|
// Save the user's original prompt to a separate file
|
|
478
636
|
if (userPrompt) {
|
|
479
637
|
const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
|
|
@@ -498,11 +656,24 @@ function printStep1(root, feature, userPrompt) {
|
|
|
498
656
|
console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
|
|
499
657
|
console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
|
|
500
658
|
checkbox("Summarize what you'll build in plain language the user can verify against their vision");
|
|
501
|
-
|
|
502
|
-
console.log(chalk.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
console.log(chalk.
|
|
659
|
+
checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
|
|
660
|
+
console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
|
|
661
|
+
checkbox('Set the project title and description for the App tab:');
|
|
662
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
663
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
664
|
+
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
665
|
+
console.log();
|
|
666
|
+
const designSystem = readDesignSystem(root);
|
|
667
|
+
if (designSystem) {
|
|
668
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
669
|
+
console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
|
|
670
|
+
console.log();
|
|
671
|
+
console.log(designSystem);
|
|
672
|
+
console.log();
|
|
673
|
+
}
|
|
674
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
675
|
+
console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
|
|
676
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
506
677
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
507
678
|
console.log();
|
|
508
679
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
@@ -527,6 +698,7 @@ function printStep1(root, feature, userPrompt) {
|
|
|
527
698
|
// ─── Step 2: Prototype ────────────────────────────────────────────────
|
|
528
699
|
function printStep2(root, feature) {
|
|
529
700
|
const port = getServerPort();
|
|
701
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
530
702
|
const projectExists = hasProject(root);
|
|
531
703
|
const prevState = readState(root);
|
|
532
704
|
const isResuming = prevState?.step === 2;
|
|
@@ -537,6 +709,8 @@ function printStep2(root, feature) {
|
|
|
537
709
|
label: STEP_LABELS[2],
|
|
538
710
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
539
711
|
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
712
|
+
appFormats: prevState?.appFormats,
|
|
713
|
+
techStackId: prevState?.techStackId,
|
|
540
714
|
});
|
|
541
715
|
logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
|
|
542
716
|
stepHeader(2, 'Prototype', feature);
|
|
@@ -562,7 +736,13 @@ function printStep2(root, feature) {
|
|
|
562
736
|
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
563
737
|
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
564
738
|
console.log();
|
|
565
|
-
console.log(chalk.
|
|
739
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
740
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
741
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
742
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
743
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
744
|
+
console.log();
|
|
745
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
566
746
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
567
747
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
568
748
|
console.log();
|
|
@@ -571,17 +751,48 @@ function printStep2(root, feature) {
|
|
|
571
751
|
console.log(chalk.bold('Checklist:'));
|
|
572
752
|
checkbox('Create API routes that read from the database via Prisma');
|
|
573
753
|
checkbox('Seed the database with demo data');
|
|
574
|
-
checkbox('
|
|
575
|
-
console.log(chalk.dim('
|
|
576
|
-
console.log(chalk.dim(
|
|
577
|
-
console.log(chalk.dim(
|
|
754
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
755
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
756
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
757
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
578
758
|
checkbox('Verify the dev server shows the changes');
|
|
759
|
+
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
760
|
+
// Responsive design guidance when building a mobile-responsive web app
|
|
761
|
+
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
762
|
+
console.log();
|
|
763
|
+
console.log(chalk.bold.magenta('Responsive Design (mobile-responsive-web-app):'));
|
|
764
|
+
console.log(chalk.magenta(' This app must look great on BOTH desktop AND mobile. It is NOT a mobile-only app.'));
|
|
765
|
+
console.log(chalk.magenta(' Design desktop-first with a full-width layout, then ensure it adapts gracefully to mobile.'));
|
|
766
|
+
console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
|
|
767
|
+
console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
|
|
768
|
+
}
|
|
769
|
+
const designSystem = readDesignSystem(root);
|
|
770
|
+
if (designSystem) {
|
|
771
|
+
console.log();
|
|
772
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
773
|
+
console.log();
|
|
774
|
+
console.log(designSystem);
|
|
775
|
+
console.log();
|
|
776
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
777
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
778
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
779
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
780
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
781
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
782
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
783
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
784
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
785
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
786
|
+
}
|
|
579
787
|
console.log();
|
|
580
788
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
581
789
|
console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
|
|
582
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
790
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
583
791
|
console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
|
|
584
792
|
console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
|
|
793
|
+
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
794
|
+
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
795
|
+
printDimensionGuidance(dim, dimNames);
|
|
585
796
|
console.log();
|
|
586
797
|
console.log(chalk.bold('Verify the dev server:'));
|
|
587
798
|
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
@@ -592,18 +803,29 @@ function printStep2(root, feature) {
|
|
|
592
803
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
593
804
|
checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
|
|
594
805
|
checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
|
|
595
|
-
checkbox('Check for broken images:
|
|
596
|
-
console.log(chalk.dim('
|
|
806
|
+
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
807
|
+
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
808
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
597
809
|
console.log(chalk.dim(' Fix or replace any broken image URLs in the seed data before proceeding.'));
|
|
598
810
|
checkbox('Check the dev server terminal output for runtime errors (missing modules, failed imports)');
|
|
811
|
+
checkbox('Verify the live preview renders: `codeyam editor client-errors`');
|
|
812
|
+
console.log(chalk.dim(' If `hasContent=false` or `liveErrors>0`, the preview is broken — fix the issue before proceeding.'));
|
|
813
|
+
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
599
814
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
600
815
|
console.log();
|
|
816
|
+
console.log(chalk.bold('Update README and setup script:'));
|
|
817
|
+
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
818
|
+
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
819
|
+
console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
|
|
820
|
+
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
821
|
+
console.log();
|
|
601
822
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
602
823
|
stopGate(2);
|
|
603
824
|
}
|
|
604
825
|
// ─── Step 3: Confirm ──────────────────────────────────────────────────
|
|
605
826
|
function printStep3(root, feature) {
|
|
606
827
|
const port = getServerPort();
|
|
828
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
607
829
|
const prevState = readState(root);
|
|
608
830
|
const isResuming = prevState?.step === 3;
|
|
609
831
|
const now = new Date().toISOString();
|
|
@@ -622,23 +844,34 @@ function printStep3(root, feature) {
|
|
|
622
844
|
console.log('Summarize what was built and get user confirmation.');
|
|
623
845
|
console.log();
|
|
624
846
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
625
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
|
|
847
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
626
848
|
checkbox('Verify API routes return valid data (curl each route)');
|
|
627
849
|
console.log();
|
|
628
850
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
629
|
-
checkbox('Run `codeyam editor verify-images \'{"paths":["/","
|
|
630
|
-
console.log(chalk.dim('
|
|
851
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
852
|
+
console.log(chalk.dim(' Include ALL page paths and ALL image URLs from seed data / API responses.'));
|
|
853
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images.'));
|
|
631
854
|
checkbox('Fix or remove any image that returns non-200 before continuing');
|
|
855
|
+
checkbox('Check for client-side errors: `codeyam editor client-errors`');
|
|
856
|
+
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
857
|
+
checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
|
|
632
858
|
console.log();
|
|
633
859
|
console.log(chalk.bold('Then present to the user:'));
|
|
634
860
|
checkbox('Summarize what was built (routes, components, data)');
|
|
635
|
-
checkbox(
|
|
861
|
+
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
862
|
+
printDimensionGuidance(dim, dimNames);
|
|
863
|
+
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
864
|
+
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
865
|
+
console.log();
|
|
866
|
+
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
867
|
+
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
868
|
+
checkbox('Prompt the user to try key interactions: forms, navigation, buttons, etc.');
|
|
869
|
+
checkbox('Ask the user: "Does everything work as expected?"');
|
|
636
870
|
console.log();
|
|
637
|
-
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion):'));
|
|
638
|
-
console.log(chalk.green(' "
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
chalk.dim(' — user describes changes, you make them, then re-present'));
|
|
871
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
872
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
|
|
873
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
874
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
|
|
642
875
|
console.log();
|
|
643
876
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
644
877
|
stopGate(3, { confirm: true });
|
|
@@ -696,6 +929,7 @@ function printStep4(root, feature) {
|
|
|
696
929
|
// ─── Step 5: Extract ──────────────────────────────────────────────────
|
|
697
930
|
function printStep5(root, feature) {
|
|
698
931
|
const port = getServerPort();
|
|
932
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
699
933
|
const prevState = readState(root);
|
|
700
934
|
const isResuming = prevState?.step === 5;
|
|
701
935
|
const now = new Date().toISOString();
|
|
@@ -719,12 +953,13 @@ function printStep5(root, feature) {
|
|
|
719
953
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
720
954
|
console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
|
|
721
955
|
console.log();
|
|
722
|
-
console.log(chalk.bold('Library functions (TDD):'));
|
|
723
|
-
checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
956
|
+
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
957
|
+
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
724
958
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
725
959
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
960
|
+
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
726
961
|
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
727
|
-
console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step 7 only captures component screenshots.'));
|
|
962
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
|
|
728
963
|
console.log();
|
|
729
964
|
console.log(chalk.bold('Recursive pass:'));
|
|
730
965
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
@@ -736,7 +971,8 @@ function printStep5(root, feature) {
|
|
|
736
971
|
checkbox('Run all tests and verify they pass');
|
|
737
972
|
checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
|
|
738
973
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
739
|
-
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
|
|
974
|
+
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
975
|
+
printDimensionGuidance(dim, dimNames);
|
|
740
976
|
console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
|
|
741
977
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
742
978
|
console.log();
|
|
@@ -766,7 +1002,8 @@ function printStep6(root, feature) {
|
|
|
766
1002
|
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
767
1003
|
checkbox('Add an entry for each new function/component extracted in step 5');
|
|
768
1004
|
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
769
|
-
checkbox('
|
|
1005
|
+
checkbox('Every non-component entry MUST have `testFile` set — hooks and functions all need tests');
|
|
1006
|
+
console.log(chalk.yellow(' If a function/hook has no test yet, go back and write one (TDD). The audit will block you.'));
|
|
770
1007
|
console.log();
|
|
771
1008
|
console.log(chalk.bold('Entry format:'));
|
|
772
1009
|
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
@@ -782,6 +1019,7 @@ function printStep6(root, feature) {
|
|
|
782
1019
|
// ─── Step 7: Analyze ──────────────────────────────────────────────────
|
|
783
1020
|
function printStep7(root, feature) {
|
|
784
1021
|
const port = getServerPort();
|
|
1022
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
785
1023
|
const prevState = readState(root);
|
|
786
1024
|
const isResuming = prevState?.step === 7;
|
|
787
1025
|
const now = new Date().toISOString();
|
|
@@ -801,9 +1039,9 @@ function printStep7(root, feature) {
|
|
|
801
1039
|
console.log();
|
|
802
1040
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
803
1041
|
checkbox('List all files with new/modified visual components from step 5');
|
|
804
|
-
checkbox('
|
|
805
|
-
console.log(chalk.dim('
|
|
806
|
-
console.log(chalk.dim('
|
|
1042
|
+
checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
|
|
1043
|
+
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
1044
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
807
1045
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
808
1046
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
809
1047
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
@@ -818,8 +1056,8 @@ function printStep7(root, feature) {
|
|
|
818
1056
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
819
1057
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
820
1058
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
821
|
-
console.log(chalk.dim(' Remix: app/routes/
|
|
822
|
-
console.log(chalk.dim(' Next.js: app/
|
|
1059
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
1060
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
823
1061
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
824
1062
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
825
1063
|
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
@@ -832,11 +1070,21 @@ function printStep7(root, feature) {
|
|
|
832
1070
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
833
1071
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
834
1072
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
835
|
-
console.log(chalk.dim(` "url":"/
|
|
1073
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
1074
|
+
console.log(chalk.dim(` "dimensions":["${dim}"],`));
|
|
836
1075
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1076
|
+
console.log(chalk.yellow(' ALWAYS include "dimensions" — use the named screen size that matches the component\'s context.'));
|
|
1077
|
+
console.log(chalk.dim(dimNames.length > 0
|
|
1078
|
+
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1079
|
+
: ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
|
|
1080
|
+
console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
|
|
1081
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
1082
|
+
console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
|
|
837
1083
|
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
838
1084
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
839
1085
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
1086
|
+
console.log(chalk.dim(' For large JSON: use your Write tool to create .codeyam/tmp/scenario.json,'));
|
|
1087
|
+
console.log(chalk.dim(' then: codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
840
1088
|
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
841
1089
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
842
1090
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
@@ -850,12 +1098,16 @@ function printStep7(root, feature) {
|
|
|
850
1098
|
console.log();
|
|
851
1099
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
852
1100
|
console.log();
|
|
853
|
-
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
|
|
1101
|
+
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1102
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
|
|
1103
|
+
console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
|
|
1104
|
+
console.log();
|
|
854
1105
|
stopGate(7);
|
|
855
1106
|
}
|
|
856
1107
|
// ─── Step 8: App Scenarios ────────────────────────────────────────────
|
|
857
1108
|
function printStep8(root, feature) {
|
|
858
1109
|
const port = getServerPort();
|
|
1110
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
859
1111
|
const prevState = readState(root);
|
|
860
1112
|
const isResuming = prevState?.step === 8;
|
|
861
1113
|
const now = new Date().toISOString();
|
|
@@ -871,26 +1123,83 @@ function printStep8(root, feature) {
|
|
|
871
1123
|
if (isResuming) {
|
|
872
1124
|
printResumptionHeader(8);
|
|
873
1125
|
}
|
|
874
|
-
console.log('Create app-level scenarios
|
|
1126
|
+
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1127
|
+
console.log();
|
|
1128
|
+
console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
|
|
1129
|
+
console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
|
|
1130
|
+
console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
|
|
1131
|
+
console.log();
|
|
1132
|
+
console.log(chalk.bold.cyan('Make seed data work hard:'));
|
|
1133
|
+
console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
|
|
1134
|
+
console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
|
|
1135
|
+
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1136
|
+
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1137
|
+
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1138
|
+
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1139
|
+
console.log();
|
|
1140
|
+
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
1141
|
+
console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
|
|
1142
|
+
console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
|
|
1143
|
+
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1144
|
+
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
875
1145
|
console.log();
|
|
876
1146
|
console.log(chalk.bold('Checklist:'));
|
|
877
|
-
checkbox('
|
|
878
|
-
|
|
879
|
-
console.log(chalk.
|
|
880
|
-
checkbox('
|
|
881
|
-
console.log(chalk.dim('
|
|
882
|
-
console.log(chalk.
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
console.log(chalk.dim(
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1147
|
+
checkbox('Run `codeyam editor scenarios` — review all existing scenarios');
|
|
1148
|
+
checkbox("Create new scenarios for any pages that don't have scenarios yet");
|
|
1149
|
+
console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
|
|
1150
|
+
checkbox('Re-register every existing scenario whose page was affected by this feature');
|
|
1151
|
+
console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
|
|
1152
|
+
console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
|
|
1153
|
+
checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
|
|
1154
|
+
console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
|
|
1155
|
+
console.log(chalk.dim(dimNames.length > 0
|
|
1156
|
+
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1157
|
+
: ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
|
|
1158
|
+
if (dimNames.length > 1) {
|
|
1159
|
+
const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
|
|
1160
|
+
console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
|
|
1161
|
+
}
|
|
1162
|
+
console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
|
|
1163
|
+
console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
|
|
1164
|
+
checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
|
|
1165
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
|
|
1166
|
+
console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
|
|
1167
|
+
checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
|
|
1168
|
+
console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
|
|
1169
|
+
console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
|
|
1170
|
+
console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
|
|
1171
|
+
checkbox('For large seed data, use your Write tool to create .codeyam/tmp/scenario.json, then:');
|
|
1172
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
1173
|
+
console.log(chalk.yellow(' IMPORTANT: Use the Write tool — NOT cat/heredoc — to avoid permission prompts'));
|
|
1174
|
+
checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
|
|
1175
|
+
console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
|
|
1176
|
+
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
1177
|
+
checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
|
|
1178
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
|
|
1179
|
+
console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
|
|
1180
|
+
console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
|
|
1181
|
+
checkbox('If no database and no localStorage, use component-style mock scenarios:');
|
|
1182
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
889
1183
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
890
1184
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
891
1185
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1186
|
+
checkbox('CRITICAL: After EACH registration, view the captured screenshot to verify it');
|
|
1187
|
+
console.log(chalk.yellow(' Read the screenshot file path from the registration response and view it.'));
|
|
1188
|
+
console.log(chalk.yellow(' Does the screenshot actually SHOW the data you put in? If you seeded 5 articles'));
|
|
1189
|
+
console.log(chalk.yellow(' but the screenshot shows an empty page, the scenario is broken — fix the code or data.'));
|
|
1190
|
+
console.log(chalk.yellow(' Do NOT create scenarios with data the app cannot yet render.'));
|
|
892
1191
|
console.log();
|
|
893
1192
|
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
|
|
1193
|
+
console.log();
|
|
1194
|
+
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1195
|
+
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
1196
|
+
console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
|
|
1197
|
+
console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
|
|
1198
|
+
console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
|
|
1199
|
+
console.log();
|
|
1200
|
+
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1201
|
+
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1202
|
+
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
894
1203
|
stopGate(8);
|
|
895
1204
|
}
|
|
896
1205
|
// ─── Step 9: User Scenarios ───────────────────────────────────────────
|
|
@@ -911,29 +1220,37 @@ function printStep9(root, feature) {
|
|
|
911
1220
|
if (isResuming) {
|
|
912
1221
|
printResumptionHeader(9);
|
|
913
1222
|
}
|
|
914
|
-
console.log('Create per-persona
|
|
1223
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
|
|
915
1224
|
console.log();
|
|
916
|
-
console.log(chalk.bold('If the app has users:'));
|
|
917
|
-
|
|
918
|
-
console.log(
|
|
919
|
-
console.log(chalk.
|
|
920
|
-
|
|
921
|
-
console.log(chalk.
|
|
922
|
-
|
|
923
|
-
console.log(chalk.dim(
|
|
1225
|
+
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1226
|
+
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
1227
|
+
console.log();
|
|
1228
|
+
console.log(chalk.bold('If the app has users/auth:'));
|
|
1229
|
+
console.log();
|
|
1230
|
+
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1231
|
+
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1232
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
|
|
1233
|
+
console.log();
|
|
1234
|
+
console.log(chalk.bold('Checklist:'));
|
|
1235
|
+
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1236
|
+
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1237
|
+
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1238
|
+
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1239
|
+
console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1240
|
+
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1241
|
+
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1242
|
+
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
1243
|
+
console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
|
|
924
1244
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
925
1245
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
926
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
927
|
-
console.log();
|
|
928
|
-
console.log(chalk.bold('If the app has NO users:'));
|
|
929
|
-
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
930
1246
|
console.log();
|
|
931
|
-
console.log(chalk.dim('
|
|
1247
|
+
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
932
1248
|
stopGate(9);
|
|
933
1249
|
}
|
|
934
1250
|
// ─── Step 10: Verify ──────────────────────────────────────────────────
|
|
935
1251
|
function printStep10(root, feature) {
|
|
936
1252
|
const port = getServerPort();
|
|
1253
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
937
1254
|
const prevState = readState(root);
|
|
938
1255
|
const isResuming = prevState?.step === 10;
|
|
939
1256
|
const now = new Date().toISOString();
|
|
@@ -958,13 +1275,16 @@ function printStep10(root, feature) {
|
|
|
958
1275
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
|
|
959
1276
|
console.log();
|
|
960
1277
|
console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
|
|
961
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1278
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1279
|
+
printDimensionGuidance(dim, dimNames);
|
|
962
1280
|
checkbox('Click through each app-level and user-persona scenario in the preview');
|
|
1281
|
+
checkbox('For seed-based scenarios: verify data renders correctly after switching');
|
|
1282
|
+
console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
|
|
963
1283
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
964
1284
|
console.log(chalk.yellow(' If `hasErrors` is true: list each scenario with errors, fix the source code,'));
|
|
965
1285
|
console.log(chalk.yellow(' re-register the affected scenarios, and re-check until hasErrors is false'));
|
|
966
1286
|
console.log(chalk.dim(' Common errors: React errors, failed fetches, undefined references, hydration mismatches'));
|
|
967
|
-
checkbox('Verify no broken images:
|
|
1287
|
+
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
968
1288
|
console.log();
|
|
969
1289
|
console.log(chalk.bold('Library functions — test check:'));
|
|
970
1290
|
checkbox('Re-run all test files from step 5 to confirm they still pass');
|
|
@@ -1009,6 +1329,7 @@ function printStep11(root, feature) {
|
|
|
1009
1329
|
// ─── Step 12: Review ──────────────────────────────────────────────────
|
|
1010
1330
|
function printStep12(root, feature) {
|
|
1011
1331
|
const port = getServerPort();
|
|
1332
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1012
1333
|
const prevState = readState(root);
|
|
1013
1334
|
const isResuming = prevState?.step === 12;
|
|
1014
1335
|
const now = new Date().toISOString();
|
|
@@ -1027,12 +1348,13 @@ function printStep12(root, feature) {
|
|
|
1027
1348
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1028
1349
|
console.log();
|
|
1029
1350
|
console.log(chalk.bold('Checklist (do all of this silently):'));
|
|
1030
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1351
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1352
|
+
printDimensionGuidance(dim, dimNames);
|
|
1031
1353
|
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
1032
1354
|
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
1033
1355
|
checkbox(`Check for client errors: \`codeyam editor client-errors\``);
|
|
1034
1356
|
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1035
|
-
checkbox('Run `codeyam editor verify-images \'{"paths":["/","
|
|
1357
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1036
1358
|
checkbox('Fix or remove any broken images before continuing');
|
|
1037
1359
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1038
1360
|
checkbox('Do not proceed until all checks pass');
|
|
@@ -1041,6 +1363,7 @@ function printStep12(root, feature) {
|
|
|
1041
1363
|
// ─── Step 13: Present ─────────────────────────────────────────────────
|
|
1042
1364
|
function printStep13(root, feature) {
|
|
1043
1365
|
const port = getServerPort();
|
|
1366
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1044
1367
|
const prevState = readState(root);
|
|
1045
1368
|
const isResuming = prevState?.step === 13;
|
|
1046
1369
|
const now = new Date().toISOString();
|
|
@@ -1067,26 +1390,21 @@ function printStep13(root, feature) {
|
|
|
1067
1390
|
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1068
1391
|
checkbox('Switch the preview to that scenario using its `id`:');
|
|
1069
1392
|
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1393
|
+
console.log(chalk.dim(" The preview auto-switches to that scenario's viewport dimension — pick one whose size best shows off the feature."));
|
|
1070
1394
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1071
1395
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1072
1396
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1073
1397
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1074
1398
|
checkbox('Report test count and audit status (one line)');
|
|
1075
1399
|
console.log();
|
|
1076
|
-
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion):'));
|
|
1077
|
-
console.log(chalk.green(' "Save & commit"') +
|
|
1400
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1401
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1078
1402
|
chalk.dim(' — git commit all changes and record in journal'));
|
|
1079
|
-
console.log(chalk.yellow(' "I\'d like to make some changes"') +
|
|
1403
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1080
1404
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1081
1405
|
console.log();
|
|
1082
1406
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1083
|
-
checkbox(
|
|
1084
|
-
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1085
|
-
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1086
|
-
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1087
|
-
console.log(chalk.green(' Then run: ') +
|
|
1088
|
-
chalk.bold('codeyam editor steps') +
|
|
1089
|
-
chalk.green(' to start the next feature'));
|
|
1407
|
+
checkbox('Advance to the commit step: `codeyam editor 14`');
|
|
1090
1408
|
console.log();
|
|
1091
1409
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1092
1410
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1096,6 +1414,89 @@ function printStep13(root, feature) {
|
|
|
1096
1414
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1097
1415
|
stopGate(13, { confirm: true });
|
|
1098
1416
|
}
|
|
1417
|
+
// ─── Step 14: Commit ─────────────────────────────────────────────────
|
|
1418
|
+
function printStep14(root, feature) {
|
|
1419
|
+
const prevState = readState(root);
|
|
1420
|
+
const isResuming = prevState?.step === 14;
|
|
1421
|
+
const now = new Date().toISOString();
|
|
1422
|
+
writeState(root, {
|
|
1423
|
+
feature,
|
|
1424
|
+
step: 14,
|
|
1425
|
+
label: STEP_LABELS[14],
|
|
1426
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1427
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1428
|
+
});
|
|
1429
|
+
logEvent(root, 'step', { step: 14, label: 'Commit', feature });
|
|
1430
|
+
stepHeader(14, 'Commit', feature);
|
|
1431
|
+
if (isResuming) {
|
|
1432
|
+
printResumptionHeader(14);
|
|
1433
|
+
}
|
|
1434
|
+
console.log('Commit all changes for this feature.');
|
|
1435
|
+
console.log();
|
|
1436
|
+
console.log(chalk.bold('Checklist:'));
|
|
1437
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
1438
|
+
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1439
|
+
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1440
|
+
stopGate(14);
|
|
1441
|
+
}
|
|
1442
|
+
// ─── Step 15: Finalize ───────────────────────────────────────────────
|
|
1443
|
+
function printStep15(root, feature) {
|
|
1444
|
+
const prevState = readState(root);
|
|
1445
|
+
const isResuming = prevState?.step === 15;
|
|
1446
|
+
const now = new Date().toISOString();
|
|
1447
|
+
writeState(root, {
|
|
1448
|
+
feature,
|
|
1449
|
+
step: 15,
|
|
1450
|
+
label: STEP_LABELS[15],
|
|
1451
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1452
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1453
|
+
});
|
|
1454
|
+
logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
|
|
1455
|
+
stepHeader(15, 'Finalize', feature);
|
|
1456
|
+
if (isResuming) {
|
|
1457
|
+
printResumptionHeader(15);
|
|
1458
|
+
}
|
|
1459
|
+
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
1460
|
+
console.log();
|
|
1461
|
+
console.log(chalk.bold('Checklist:'));
|
|
1462
|
+
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1463
|
+
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
1464
|
+
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
1465
|
+
stopGate(15);
|
|
1466
|
+
}
|
|
1467
|
+
// ─── Step 16: Push ───────────────────────────────────────────────────
|
|
1468
|
+
function printStep16(root, feature) {
|
|
1469
|
+
const prevState = readState(root);
|
|
1470
|
+
const isResuming = prevState?.step === 16;
|
|
1471
|
+
const now = new Date().toISOString();
|
|
1472
|
+
writeState(root, {
|
|
1473
|
+
feature,
|
|
1474
|
+
step: 16,
|
|
1475
|
+
label: STEP_LABELS[16],
|
|
1476
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1477
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1478
|
+
});
|
|
1479
|
+
logEvent(root, 'step', { step: 16, label: 'Push', feature });
|
|
1480
|
+
stepHeader(16, 'Push', feature);
|
|
1481
|
+
if (isResuming) {
|
|
1482
|
+
printResumptionHeader(16);
|
|
1483
|
+
}
|
|
1484
|
+
console.log('Push the commit to the remote repository.');
|
|
1485
|
+
console.log();
|
|
1486
|
+
console.log(chalk.bold('Checklist:'));
|
|
1487
|
+
checkbox('Check if a git remote is configured: `git remote -v`');
|
|
1488
|
+
checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
|
|
1489
|
+
console.log(chalk.dim(' If a remote exists, ask (AskUserQuestion): "Would you like me to push this commit to the remote?" with options "Yes, push" and "No, skip"'));
|
|
1490
|
+
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
1491
|
+
console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
|
|
1492
|
+
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
1493
|
+
checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
|
|
1494
|
+
console.log();
|
|
1495
|
+
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
1496
|
+
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
1497
|
+
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
1498
|
+
stopGate(16, { confirm: true });
|
|
1499
|
+
}
|
|
1099
1500
|
// ─── Command definition ───────────────────────────────────────────────
|
|
1100
1501
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
1101
1502
|
/**
|
|
@@ -1104,11 +1505,15 @@ function printStep13(root, feature) {
|
|
|
1104
1505
|
* Runs data-structure-only analysis for all glossary entities, then outputs
|
|
1105
1506
|
* an import graph and entity data structures as JSON to stdout.
|
|
1106
1507
|
*/
|
|
1107
|
-
async function handleAnalyzeImports() {
|
|
1508
|
+
async function handleAnalyzeImports(options = {}) {
|
|
1108
1509
|
const root = getProjectRoot();
|
|
1109
1510
|
// Read glossary
|
|
1110
1511
|
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
1111
1512
|
if (!fs.existsSync(glossaryPath)) {
|
|
1513
|
+
if (options.silent) {
|
|
1514
|
+
// Internal caller — glossary doesn't exist yet, nothing to analyze
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1112
1517
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
1113
1518
|
console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
|
|
1114
1519
|
process.exit(1);
|
|
@@ -1126,15 +1531,24 @@ async function handleAnalyzeImports() {
|
|
|
1126
1531
|
process.exit(1);
|
|
1127
1532
|
}
|
|
1128
1533
|
const filePaths = glossaryEntries.map((e) => e.filePath);
|
|
1129
|
-
|
|
1534
|
+
// Include page files so pages get analyzed as entities too.
|
|
1535
|
+
// This allows the import graph to map page names to their dependencies.
|
|
1536
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
1537
|
+
const pageFilePaths = scanPageFilePaths(root);
|
|
1538
|
+
for (const pageFile of Object.values(pageFilePaths)) {
|
|
1539
|
+
if (!filePaths.includes(pageFile)) {
|
|
1540
|
+
filePaths.push(pageFile);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1130
1543
|
const progress = new ProgressReporter();
|
|
1131
|
-
// Run data-structure-only analysis for all entities
|
|
1132
|
-
|
|
1544
|
+
// Run data-structure-only analysis for all entities (glossary + pages)
|
|
1545
|
+
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
1546
|
+
// The analyzer will discover and create them from file paths.
|
|
1547
|
+
progress.start('Running import analysis for all entities...');
|
|
1133
1548
|
try {
|
|
1134
1549
|
await runAnalysisForEntities({
|
|
1135
1550
|
projectRoot: root,
|
|
1136
1551
|
filePaths,
|
|
1137
|
-
entityNames,
|
|
1138
1552
|
progress,
|
|
1139
1553
|
onlyDataStructure: true,
|
|
1140
1554
|
});
|
|
@@ -1151,8 +1565,11 @@ async function handleAnalyzeImports() {
|
|
|
1151
1565
|
await initializeEnvironment();
|
|
1152
1566
|
const entities = await loadEntities({});
|
|
1153
1567
|
if (!entities || entities.length === 0) {
|
|
1154
|
-
progress.
|
|
1155
|
-
|
|
1568
|
+
progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
|
|
1569
|
+
if (!options.silent) {
|
|
1570
|
+
console.log(JSON.stringify({ imports: {}, entities: {} }));
|
|
1571
|
+
}
|
|
1572
|
+
return;
|
|
1156
1573
|
}
|
|
1157
1574
|
// Deduplicate to latest versions
|
|
1158
1575
|
const latestByKey = new Map();
|
|
@@ -1166,7 +1583,7 @@ async function handleAnalyzeImports() {
|
|
|
1166
1583
|
latestByKey.set(key, entity);
|
|
1167
1584
|
}
|
|
1168
1585
|
}
|
|
1169
|
-
const entityNameSet = new Set(
|
|
1586
|
+
const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
|
|
1170
1587
|
const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
|
|
1171
1588
|
// Build import graph from importedExports metadata
|
|
1172
1589
|
const imports = {};
|
|
@@ -1204,11 +1621,211 @@ async function handleAnalyzeImports() {
|
|
|
1204
1621
|
};
|
|
1205
1622
|
}
|
|
1206
1623
|
progress.succeed('Done');
|
|
1207
|
-
// Output combined JSON
|
|
1208
|
-
|
|
1209
|
-
|
|
1624
|
+
// Output combined JSON (suppressed when called internally during startup)
|
|
1625
|
+
if (!options.silent) {
|
|
1626
|
+
const summary = formatApiSubcommandResult('analyze-imports', {
|
|
1627
|
+
imports,
|
|
1628
|
+
entities: entityData,
|
|
1629
|
+
});
|
|
1630
|
+
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
1634
|
+
/**
|
|
1635
|
+
* Validate seed data structure and check for a seed adapter.
|
|
1636
|
+
* Usage: codeyam editor validate-seed '{"products":[...]}'
|
|
1637
|
+
*/
|
|
1638
|
+
function handleValidateSeed(jsonArg) {
|
|
1639
|
+
const root = process.cwd();
|
|
1640
|
+
// Check for seed adapter
|
|
1641
|
+
const adapterPath = detectSeedAdapter(root);
|
|
1642
|
+
if (adapterPath) {
|
|
1643
|
+
console.log(chalk.green(`Seed adapter found: ${adapterPath}`));
|
|
1644
|
+
}
|
|
1645
|
+
else {
|
|
1646
|
+
console.log(chalk.yellow('No seed adapter found. Create .codeyam/seed-adapter.ts to use seed-based scenarios.'));
|
|
1647
|
+
}
|
|
1648
|
+
if (!jsonArg) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
let data;
|
|
1652
|
+
try {
|
|
1653
|
+
data = JSON.parse(jsonArg);
|
|
1654
|
+
}
|
|
1655
|
+
catch {
|
|
1656
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
1657
|
+
process.exit(1);
|
|
1658
|
+
}
|
|
1659
|
+
const result = validateSeedData(data);
|
|
1660
|
+
if (result.valid) {
|
|
1661
|
+
const tables = Object.keys(data);
|
|
1662
|
+
const totalRows = tables.reduce((sum, t) => sum + (Array.isArray(data[t]) ? data[t].length : 0), 0);
|
|
1663
|
+
console.log(chalk.green(`Seed data valid: ${tables.length} table(s), ${totalRows} total row(s)`));
|
|
1664
|
+
for (const table of tables) {
|
|
1665
|
+
const rows = Array.isArray(data[table])
|
|
1666
|
+
? data[table].length
|
|
1667
|
+
: 0;
|
|
1668
|
+
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
else {
|
|
1672
|
+
console.error(chalk.red('Seed data validation failed:'));
|
|
1673
|
+
for (const err of result.errors) {
|
|
1674
|
+
console.error(chalk.red(` - ${err}`));
|
|
1675
|
+
}
|
|
1676
|
+
process.exit(1);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
1680
|
+
/**
|
|
1681
|
+
* `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
|
|
1682
|
+
*
|
|
1683
|
+
* Creates isolation route directories and the layout guard file.
|
|
1684
|
+
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
1685
|
+
*/
|
|
1686
|
+
function handleIsolate(componentNames) {
|
|
1687
|
+
const root = process.cwd();
|
|
1688
|
+
if (componentNames.length === 0) {
|
|
1689
|
+
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
1690
|
+
process.exit(1);
|
|
1691
|
+
}
|
|
1692
|
+
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
1693
|
+
// Create layout.tsx with production guard if missing
|
|
1694
|
+
const layoutPath = path.join(isolateDir, 'layout.tsx');
|
|
1695
|
+
if (!fs.existsSync(layoutPath)) {
|
|
1696
|
+
fs.mkdirSync(isolateDir, { recursive: true });
|
|
1697
|
+
fs.writeFileSync(layoutPath, [
|
|
1698
|
+
'import { notFound } from "next/navigation";',
|
|
1699
|
+
'',
|
|
1700
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
1701
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
1702
|
+
' return <>{children}</>;',
|
|
1703
|
+
'}',
|
|
1704
|
+
'',
|
|
1705
|
+
].join('\n'), 'utf8');
|
|
1706
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
|
|
1707
|
+
}
|
|
1708
|
+
// Create a directory for each component
|
|
1709
|
+
const created = [];
|
|
1710
|
+
const existed = [];
|
|
1711
|
+
for (const name of componentNames) {
|
|
1712
|
+
const dir = path.join(isolateDir, name);
|
|
1713
|
+
if (fs.existsSync(dir)) {
|
|
1714
|
+
existed.push(name);
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1718
|
+
created.push(name);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (created.length > 0) {
|
|
1722
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
1723
|
+
}
|
|
1724
|
+
if (existed.length > 0) {
|
|
1725
|
+
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
1726
|
+
}
|
|
1210
1727
|
}
|
|
1211
1728
|
// ─── Register subcommand ──────────────────────────────────────────────
|
|
1729
|
+
/**
|
|
1730
|
+
* Format API subcommand results as concise one-line summaries.
|
|
1731
|
+
* Prevents Claude from piping output to python3 just to extract key fields.
|
|
1732
|
+
* Returns null for subcommands that should keep raw JSON output.
|
|
1733
|
+
*/
|
|
1734
|
+
function formatApiSubcommandResult(subcommand, data) {
|
|
1735
|
+
if (!data || typeof data !== 'object')
|
|
1736
|
+
return null;
|
|
1737
|
+
switch (subcommand) {
|
|
1738
|
+
case 'journal': {
|
|
1739
|
+
const parts = [`success=${data.success}`];
|
|
1740
|
+
if (data.entry?.time)
|
|
1741
|
+
parts.push(`time="${data.entry.time}"`);
|
|
1742
|
+
if (data.entry?.title)
|
|
1743
|
+
parts.push(`title="${data.entry.title}"`);
|
|
1744
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
1745
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
1746
|
+
}
|
|
1747
|
+
return parts.join(' ');
|
|
1748
|
+
}
|
|
1749
|
+
case 'journal-update': {
|
|
1750
|
+
const parts = [`success=${data.success}`];
|
|
1751
|
+
if (data.entry?.time)
|
|
1752
|
+
parts.push(`time="${data.entry.time}"`);
|
|
1753
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
1754
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
1755
|
+
}
|
|
1756
|
+
return parts.join(' ');
|
|
1757
|
+
}
|
|
1758
|
+
case 'commit': {
|
|
1759
|
+
const parts = [`success=${data.success}`];
|
|
1760
|
+
if (data.sha)
|
|
1761
|
+
parts.push(`sha=${data.sha}`);
|
|
1762
|
+
if (data.message)
|
|
1763
|
+
parts.push(`message="${data.message.split('\n')[0]}"`);
|
|
1764
|
+
return parts.join(' ');
|
|
1765
|
+
}
|
|
1766
|
+
case 'preview': {
|
|
1767
|
+
const parts = [
|
|
1768
|
+
`success=${!!data.ok || data.success !== false}`,
|
|
1769
|
+
];
|
|
1770
|
+
if (data.path)
|
|
1771
|
+
parts.push(`path="${data.path}"`);
|
|
1772
|
+
if (data.scenarioId)
|
|
1773
|
+
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
1774
|
+
if (data.sessionsNotified != null)
|
|
1775
|
+
parts.push(`notified=${data.sessionsNotified}`);
|
|
1776
|
+
return parts.join(' ');
|
|
1777
|
+
}
|
|
1778
|
+
case 'dev-server': {
|
|
1779
|
+
if (data.status) {
|
|
1780
|
+
const parts = [`status=${data.status}`, `pid=${data.pid || 'none'}`];
|
|
1781
|
+
if (data.url)
|
|
1782
|
+
parts.push(`url=${data.url}`);
|
|
1783
|
+
if (data.proxyUrl)
|
|
1784
|
+
parts.push(`proxyUrl=${data.proxyUrl}`);
|
|
1785
|
+
if (data.errorMessage)
|
|
1786
|
+
parts.push(`error: ${data.errorMessage}`);
|
|
1787
|
+
return parts.join(' ');
|
|
1788
|
+
}
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
case 'client-errors': {
|
|
1792
|
+
const parts = [`hasErrors=${data.hasErrors}`];
|
|
1793
|
+
parts.push(`totalErrors=${data.totalErrors ?? 0}`);
|
|
1794
|
+
if (data.livePreview) {
|
|
1795
|
+
const lp = data.livePreview;
|
|
1796
|
+
parts.push(`livePreview=${lp.loaded ? 'loaded' : 'not-loaded'}`);
|
|
1797
|
+
if (lp.loaded) {
|
|
1798
|
+
parts.push(`hasContent=${lp.hasContent}`);
|
|
1799
|
+
}
|
|
1800
|
+
const liveErrors = lp.errors?.length ?? 0;
|
|
1801
|
+
parts.push(`liveErrors=${liveErrors}`);
|
|
1802
|
+
if (liveErrors > 0) {
|
|
1803
|
+
// Show first few error messages for context
|
|
1804
|
+
for (const err of lp.errors.slice(0, 3)) {
|
|
1805
|
+
parts.push(` error: ${err.message}`);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
parts.push('livePreview=not-loaded');
|
|
1811
|
+
}
|
|
1812
|
+
return parts.join(' ');
|
|
1813
|
+
}
|
|
1814
|
+
case 'analyze-imports': {
|
|
1815
|
+
const importEntries = Object.entries(data.imports || {});
|
|
1816
|
+
const entityEntries = Object.entries(data.entities || {});
|
|
1817
|
+
const parts = [];
|
|
1818
|
+
parts.push(`entities=${entityEntries.length}`);
|
|
1819
|
+
parts.push(`withImports=${importEntries.length}`);
|
|
1820
|
+
if (importEntries.length > 0) {
|
|
1821
|
+
parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
|
|
1822
|
+
}
|
|
1823
|
+
return parts.join(' ');
|
|
1824
|
+
}
|
|
1825
|
+
default:
|
|
1826
|
+
return null; // journal-list, show/hide-results: keep full JSON
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1212
1829
|
/**
|
|
1213
1830
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
1214
1831
|
*
|
|
@@ -1217,38 +1834,88 @@ async function handleAnalyzeImports() {
|
|
|
1217
1834
|
*/
|
|
1218
1835
|
async function handleRegister(jsonArg) {
|
|
1219
1836
|
if (!jsonArg) {
|
|
1837
|
+
const { defaultName: dim } = getProjectDimensions(getProjectRoot());
|
|
1220
1838
|
console.error(chalk.red('Error: JSON argument required.'));
|
|
1221
|
-
console.error(chalk.dim(
|
|
1839
|
+
console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
|
|
1840
|
+
console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
|
|
1222
1841
|
process.exit(1);
|
|
1223
1842
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
1843
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
1844
|
+
if (parsed.error) {
|
|
1845
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
1846
|
+
if (parsed.error === 'Invalid JSON') {
|
|
1847
|
+
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
1848
|
+
}
|
|
1231
1849
|
process.exit(1);
|
|
1232
1850
|
}
|
|
1851
|
+
// Normalize to array for uniform handling
|
|
1852
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
1233
1853
|
const port = getServerPort();
|
|
1234
1854
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1855
|
+
const isBatch = items.length > 1;
|
|
1856
|
+
let succeeded = 0;
|
|
1857
|
+
let failed = 0;
|
|
1858
|
+
for (let i = 0; i < items.length; i++) {
|
|
1859
|
+
const body = items[i];
|
|
1860
|
+
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
1861
|
+
try {
|
|
1862
|
+
const res = await fetch(url, {
|
|
1863
|
+
method: 'POST',
|
|
1864
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1865
|
+
body: JSON.stringify(body),
|
|
1866
|
+
});
|
|
1867
|
+
const data = await res.json();
|
|
1868
|
+
// Print concise summary instead of raw JSON so Claude doesn't need python3
|
|
1869
|
+
const parts = [];
|
|
1870
|
+
parts.push(`success=${data.success}`);
|
|
1871
|
+
if (data.scenario?.name)
|
|
1872
|
+
parts.push(`name="${data.scenario.name}"`);
|
|
1873
|
+
parts.push(`screenshot=${!!data.screenshotCaptured}`);
|
|
1874
|
+
if (data.capturedViewport) {
|
|
1875
|
+
parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
|
|
1876
|
+
}
|
|
1877
|
+
if (data.scenario?.dimension)
|
|
1878
|
+
parts.push(`dimension="${data.scenario.dimension}"`);
|
|
1879
|
+
if (data.clientErrors?.length > 0) {
|
|
1880
|
+
parts.push(chalk.red(`errors=${data.clientErrors.length}`));
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
parts.push(`errors=0`);
|
|
1884
|
+
}
|
|
1885
|
+
if (data.seedResult)
|
|
1886
|
+
parts.push(`seed=${data.seedResult.success}`);
|
|
1887
|
+
if (data.captureError)
|
|
1888
|
+
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
1889
|
+
console.log(prefix + parts.join(' '));
|
|
1890
|
+
// Surface client errors prominently so they can't be missed
|
|
1891
|
+
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
1892
|
+
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
1893
|
+
for (const err of data.clientErrors) {
|
|
1894
|
+
console.log(chalk.red(` → ${err}`));
|
|
1895
|
+
}
|
|
1896
|
+
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
1897
|
+
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
1898
|
+
}
|
|
1899
|
+
if (!res.ok) {
|
|
1900
|
+
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
1901
|
+
failed++;
|
|
1902
|
+
}
|
|
1903
|
+
else {
|
|
1904
|
+
succeeded++;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
catch (error) {
|
|
1908
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1909
|
+
console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
|
|
1910
|
+
console.error(chalk.dim(` ${msg}`));
|
|
1911
|
+
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
1912
|
+
failed++;
|
|
1245
1913
|
}
|
|
1246
1914
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
1915
|
+
if (isBatch) {
|
|
1916
|
+
console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
|
|
1917
|
+
}
|
|
1918
|
+
if (failed > 0) {
|
|
1252
1919
|
process.exit(1);
|
|
1253
1920
|
}
|
|
1254
1921
|
}
|
|
@@ -1269,11 +1936,23 @@ async function handleDependents(entityName) {
|
|
|
1269
1936
|
const progress = new ProgressReporter();
|
|
1270
1937
|
progress.start('Loading entities from database...');
|
|
1271
1938
|
await initializeEnvironment();
|
|
1272
|
-
|
|
1939
|
+
let allEntities = await loadEntities({});
|
|
1273
1940
|
if (!allEntities || allEntities.length === 0) {
|
|
1274
|
-
progress.
|
|
1275
|
-
|
|
1276
|
-
|
|
1941
|
+
progress.succeed('No entities found — running analyze-imports first...');
|
|
1942
|
+
try {
|
|
1943
|
+
await handleAnalyzeImports({ silent: true });
|
|
1944
|
+
}
|
|
1945
|
+
catch {
|
|
1946
|
+
console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
1947
|
+
process.exit(1);
|
|
1948
|
+
}
|
|
1949
|
+
// Reload entities after analysis
|
|
1950
|
+
const reloaded = await loadEntities({});
|
|
1951
|
+
if (!reloaded || reloaded.length === 0) {
|
|
1952
|
+
progress.fail('No entities found even after analyze-imports');
|
|
1953
|
+
process.exit(1);
|
|
1954
|
+
}
|
|
1955
|
+
allEntities = reloaded;
|
|
1277
1956
|
}
|
|
1278
1957
|
// Find the target entity by name (case-insensitive match)
|
|
1279
1958
|
const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
@@ -1360,15 +2039,16 @@ async function handleDependents(entityName) {
|
|
|
1360
2039
|
* `codeyam editor change <feature>`
|
|
1361
2040
|
*
|
|
1362
2041
|
* Prints a condensed post-change checklist that guides Claude through
|
|
1363
|
-
* re-verifying after user-requested modifications.
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
2042
|
+
* re-verifying after user-requested modifications. When called from
|
|
2043
|
+
* step 13+, this loops back to step 13 (present). When called from an
|
|
2044
|
+
* earlier step, it returns to that step so the normal flow continues.
|
|
1366
2045
|
*/
|
|
1367
2046
|
function handleChange(feature) {
|
|
1368
2047
|
const root = getProjectRoot();
|
|
2048
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
2049
|
+
const state = readState(root);
|
|
1369
2050
|
if (!feature) {
|
|
1370
2051
|
// Try to read feature from state
|
|
1371
|
-
const state = readState(root);
|
|
1372
2052
|
if (state?.feature) {
|
|
1373
2053
|
feature = state.feature;
|
|
1374
2054
|
}
|
|
@@ -1378,6 +2058,7 @@ function handleChange(feature) {
|
|
|
1378
2058
|
process.exit(1);
|
|
1379
2059
|
}
|
|
1380
2060
|
}
|
|
2061
|
+
const currentStep = state?.step ?? 13;
|
|
1381
2062
|
const port = getServerPort();
|
|
1382
2063
|
console.log();
|
|
1383
2064
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -1385,9 +2066,19 @@ function handleChange(feature) {
|
|
|
1385
2066
|
console.log();
|
|
1386
2067
|
console.log('The user has requested changes. Follow this checklist after making them.');
|
|
1387
2068
|
console.log();
|
|
2069
|
+
const designSystem = readDesignSystem(root);
|
|
2070
|
+
if (designSystem) {
|
|
2071
|
+
console.log(chalk.bold.magenta('Design System (active):'));
|
|
2072
|
+
console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
|
|
2073
|
+
console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
|
|
2074
|
+
console.log();
|
|
2075
|
+
console.log(designSystem);
|
|
2076
|
+
console.log();
|
|
2077
|
+
}
|
|
1388
2078
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
1389
2079
|
console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
|
|
1390
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
2080
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2081
|
+
printDimensionGuidance(dim, dimNames);
|
|
1391
2082
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
1392
2083
|
console.log();
|
|
1393
2084
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
@@ -1395,8 +2086,8 @@ function handleChange(feature) {
|
|
|
1395
2086
|
console.log();
|
|
1396
2087
|
console.log(chalk.bold('1. Re-register affected component scenarios:'));
|
|
1397
2088
|
checkbox('For each component you modified, re-register ALL its scenarios');
|
|
1398
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","
|
|
1399
|
-
checkbox('For any NEW components
|
|
2089
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
|
|
2090
|
+
checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
|
|
1400
2091
|
console.log();
|
|
1401
2092
|
console.log(chalk.bold('2. Re-run affected tests:'));
|
|
1402
2093
|
checkbox('Re-run test files for any functions you modified');
|
|
@@ -1406,15 +2097,29 @@ function handleChange(feature) {
|
|
|
1406
2097
|
console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
|
|
1407
2098
|
console.log();
|
|
1408
2099
|
console.log(chalk.bold('3. Re-capture app-level scenarios:'));
|
|
1409
|
-
checkbox('
|
|
1410
|
-
checkbox(`
|
|
2100
|
+
checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
|
|
2101
|
+
checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
|
|
2102
|
+
checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
|
|
2103
|
+
console.log(chalk.dim(' Shared components (headers, navbars, layouts) affect MANY pages — re-register all of them.'));
|
|
2104
|
+
console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
|
|
2105
|
+
console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
|
|
2106
|
+
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2107
|
+
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2108
|
+
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
2109
|
+
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2110
|
+
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
1411
2111
|
console.log();
|
|
1412
2112
|
console.log(chalk.bold('4. Verify completeness:'));
|
|
1413
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1414
|
-
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
|
|
2113
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
2114
|
+
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2115
|
+
printDimensionGuidance(dim, dimNames);
|
|
2116
|
+
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
1415
2117
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
1416
2118
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
2119
|
+
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
2120
|
+
console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
|
|
1417
2121
|
checkbox('Fix any errors, then re-register affected scenarios');
|
|
2122
|
+
checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
|
|
1418
2123
|
console.log();
|
|
1419
2124
|
console.log(chalk.bold('5. Update the existing journal entry:'));
|
|
1420
2125
|
checkbox('Get existing entry: `codeyam editor journal-list`');
|
|
@@ -1423,11 +2128,46 @@ function handleChange(feature) {
|
|
|
1423
2128
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
1424
2129
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
1425
2130
|
console.log();
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
2131
|
+
// If the change was initiated from a step before 13, return to that step
|
|
2132
|
+
// instead of jumping to step 13. The change workflow should only loop to
|
|
2133
|
+
// step 13 when changes are requested FROM step 13.
|
|
2134
|
+
if (currentStep < 13) {
|
|
2135
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2136
|
+
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2137
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2138
|
+
chalk.bold(`codeyam editor ${currentStep}`));
|
|
2139
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2140
|
+
}
|
|
2141
|
+
else {
|
|
2142
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2143
|
+
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2144
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2145
|
+
chalk.bold(`codeyam editor 13`));
|
|
2146
|
+
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2147
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2148
|
+
}
|
|
1429
2149
|
console.log();
|
|
1430
2150
|
}
|
|
2151
|
+
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2152
|
+
/**
|
|
2153
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
2154
|
+
* false if any issues remain or the server is unreachable.
|
|
2155
|
+
* Used as a hard gate before steps 8+.
|
|
2156
|
+
*/
|
|
2157
|
+
async function checkAuditGate() {
|
|
2158
|
+
const port = getServerPort();
|
|
2159
|
+
try {
|
|
2160
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
2161
|
+
if (!res.ok)
|
|
2162
|
+
return false;
|
|
2163
|
+
const data = await res.json();
|
|
2164
|
+
return data?.summary?.allPassing === true;
|
|
2165
|
+
}
|
|
2166
|
+
catch {
|
|
2167
|
+
// Server not running or unreachable — can't verify, so don't block
|
|
2168
|
+
return true;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
1431
2171
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
1432
2172
|
/**
|
|
1433
2173
|
* `codeyam editor audit`
|
|
@@ -1462,10 +2202,25 @@ async function handleAudit() {
|
|
|
1462
2202
|
console.log(chalk.bold('Components (scenarios):'));
|
|
1463
2203
|
for (const c of components) {
|
|
1464
2204
|
const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
2205
|
+
let detail;
|
|
2206
|
+
if (c.status === 'has_errors') {
|
|
2207
|
+
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
2208
|
+
}
|
|
2209
|
+
else if (c.status === 'ok') {
|
|
2210
|
+
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
2211
|
+
}
|
|
2212
|
+
else {
|
|
2213
|
+
detail = chalk.red(' — no scenarios registered');
|
|
2214
|
+
}
|
|
2215
|
+
console.log(` ${icon} ${c.name}${detail}`);
|
|
2216
|
+
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
2217
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
2218
|
+
console.log(chalk.red(` → ${err}`));
|
|
2219
|
+
}
|
|
2220
|
+
if (c.clientErrors.length > 3) {
|
|
2221
|
+
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
1469
2224
|
}
|
|
1470
2225
|
console.log();
|
|
1471
2226
|
}
|
|
@@ -1507,6 +2262,9 @@ async function handleAudit() {
|
|
|
1507
2262
|
if (summary.componentsMissing > 0) {
|
|
1508
2263
|
parts.push(`${summary.componentsMissing} component${summary.componentsMissing !== 1 ? 's' : ''} missing scenarios`);
|
|
1509
2264
|
}
|
|
2265
|
+
if (summary.componentsWithErrors > 0) {
|
|
2266
|
+
parts.push(`${summary.componentsWithErrors} component${summary.componentsWithErrors !== 1 ? 's' : ''} with client errors`);
|
|
2267
|
+
}
|
|
1510
2268
|
if (summary.functionsMissing > 0) {
|
|
1511
2269
|
parts.push(`${summary.functionsMissing} function${summary.functionsMissing !== 1 ? 's' : ''} missing tests`);
|
|
1512
2270
|
}
|
|
@@ -1522,6 +2280,17 @@ async function handleAudit() {
|
|
|
1522
2280
|
if (!allOk) {
|
|
1523
2281
|
process.exit(1);
|
|
1524
2282
|
}
|
|
2283
|
+
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
2284
|
+
// so the App tab can show component/function dependencies for each page.
|
|
2285
|
+
// Previously this was a manual step that Claude had to remember to run.
|
|
2286
|
+
console.log(chalk.dim('Building import graph...'));
|
|
2287
|
+
try {
|
|
2288
|
+
await handleAnalyzeImports({ silent: true });
|
|
2289
|
+
}
|
|
2290
|
+
catch {
|
|
2291
|
+
// Non-fatal — audit passed, import graph is a bonus
|
|
2292
|
+
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
2293
|
+
}
|
|
1525
2294
|
}
|
|
1526
2295
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
1527
2296
|
async function handleScenarios() {
|
|
@@ -1609,6 +2378,77 @@ async function handleScenarios() {
|
|
|
1609
2378
|
const total = scenarios.length;
|
|
1610
2379
|
const groupCount = groups.size;
|
|
1611
2380
|
console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
|
|
2381
|
+
// Show screenshot paths so Claude can verify images with the Read tool
|
|
2382
|
+
const withScreenshots = scenarios.filter((s) => s.screenshotPath);
|
|
2383
|
+
if (withScreenshots.length > 0) {
|
|
2384
|
+
console.log();
|
|
2385
|
+
console.log(chalk.bold.cyan('Screenshots:'));
|
|
2386
|
+
for (const s of withScreenshots) {
|
|
2387
|
+
console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
console.log();
|
|
2391
|
+
}
|
|
2392
|
+
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
2393
|
+
async function handleScenarioCoverage() {
|
|
2394
|
+
const port = getServerPort();
|
|
2395
|
+
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
2396
|
+
let data;
|
|
2397
|
+
try {
|
|
2398
|
+
const res = await fetch(url);
|
|
2399
|
+
if (!res.ok) {
|
|
2400
|
+
console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
|
|
2401
|
+
process.exit(1);
|
|
2402
|
+
}
|
|
2403
|
+
data = await res.json();
|
|
2404
|
+
}
|
|
2405
|
+
catch (err) {
|
|
2406
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2407
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
2408
|
+
process.exit(1);
|
|
2409
|
+
}
|
|
2410
|
+
console.log();
|
|
2411
|
+
console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
|
|
2412
|
+
console.log();
|
|
2413
|
+
if (data.note) {
|
|
2414
|
+
console.log(chalk.yellow(data.note));
|
|
2415
|
+
console.log();
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
// Fresh scenarios (already recaptured this feature)
|
|
2419
|
+
if (data.freshScenarios.length > 0) {
|
|
2420
|
+
console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
|
|
2421
|
+
for (const s of data.freshScenarios) {
|
|
2422
|
+
console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
|
|
2423
|
+
}
|
|
2424
|
+
console.log();
|
|
2425
|
+
}
|
|
2426
|
+
// Stale scenarios (need recapturing)
|
|
2427
|
+
if (data.staleScenarios.length > 0) {
|
|
2428
|
+
console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
|
|
2429
|
+
for (const s of data.staleScenarios) {
|
|
2430
|
+
console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
|
|
2431
|
+
}
|
|
2432
|
+
console.log();
|
|
2433
|
+
console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
|
|
2434
|
+
console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
|
|
2435
|
+
console.log();
|
|
2436
|
+
}
|
|
2437
|
+
// Uncovered pages (changed but no scenarios at all)
|
|
2438
|
+
if (data.uncoveredPages.length > 0) {
|
|
2439
|
+
console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
|
|
2440
|
+
for (const p of data.uncoveredPages) {
|
|
2441
|
+
console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
|
|
2442
|
+
}
|
|
2443
|
+
console.log();
|
|
2444
|
+
}
|
|
2445
|
+
// Summary
|
|
2446
|
+
if (data.pass) {
|
|
2447
|
+
console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
|
|
2448
|
+
}
|
|
2449
|
+
else {
|
|
2450
|
+
console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
|
|
2451
|
+
}
|
|
1612
2452
|
console.log();
|
|
1613
2453
|
}
|
|
1614
2454
|
// ─── Template subcommand ─────────────────────────────────────────────
|
|
@@ -1619,20 +2459,44 @@ async function handleTemplate() {
|
|
|
1619
2459
|
console.log(chalk.yellow('Project already exists (package.json found). Skipping.'));
|
|
1620
2460
|
return;
|
|
1621
2461
|
}
|
|
1622
|
-
|
|
2462
|
+
// Determine which template to use from editor state
|
|
2463
|
+
const state = readState(root);
|
|
2464
|
+
const techStackId = state?.techStackId;
|
|
2465
|
+
const stack = techStackId
|
|
2466
|
+
? TECH_STACKS.find((s) => s.id === techStackId)
|
|
2467
|
+
: undefined;
|
|
2468
|
+
const templateName = stack?.templateDir || 'nextjs-prisma-sqlite';
|
|
2469
|
+
const templateDir = path.join(__dirname, '..', '..', 'templates', templateName);
|
|
1623
2470
|
if (!fs.existsSync(templateDir)) {
|
|
1624
2471
|
console.error(chalk.red('Error: Template directory not found at ' + templateDir));
|
|
1625
2472
|
process.exit(1);
|
|
1626
2473
|
}
|
|
1627
2474
|
const { execSync } = await import('child_process');
|
|
1628
2475
|
// 1. Copy template files
|
|
1629
|
-
console.log(chalk.bold(
|
|
2476
|
+
console.log(chalk.bold(`Copying ${stack?.name || templateName} template files...`));
|
|
1630
2477
|
execSync(`cp -r ${templateDir}/* .`, { cwd: root, stdio: 'inherit' });
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2478
|
+
// Copy dotfiles that are stored without the dot prefix in templates
|
|
2479
|
+
const gitignorePath = path.join(templateDir, 'gitignore');
|
|
2480
|
+
if (fs.existsSync(gitignorePath)) {
|
|
2481
|
+
execSync(`cp ${templateDir}/gitignore .gitignore`, {
|
|
2482
|
+
cwd: root,
|
|
2483
|
+
stdio: 'inherit',
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
const envPath = path.join(templateDir, 'env');
|
|
2487
|
+
if (fs.existsSync(envPath)) {
|
|
2488
|
+
execSync(`cp ${templateDir}/env .env`, { cwd: root, stdio: 'inherit' });
|
|
2489
|
+
}
|
|
2490
|
+
// Copy seed adapter into .codeyam/ (stored outside .codeyam/ in the template
|
|
2491
|
+
// to avoid gitignore issues — .codeyam/ is selectively ignored in user projects)
|
|
2492
|
+
const seedAdapterSrc = path.join(templateDir, 'seed-adapter.ts');
|
|
2493
|
+
if (fs.existsSync(seedAdapterSrc)) {
|
|
2494
|
+
const codeyamDir = path.join(root, '.codeyam');
|
|
2495
|
+
if (!fs.existsSync(codeyamDir)) {
|
|
2496
|
+
fs.mkdirSync(codeyamDir, { recursive: true });
|
|
2497
|
+
}
|
|
2498
|
+
fs.copyFileSync(seedAdapterSrc, path.join(codeyamDir, 'seed-adapter.ts'));
|
|
2499
|
+
}
|
|
1636
2500
|
console.log(chalk.green(' Template files copied.'));
|
|
1637
2501
|
// 2. Install dependencies
|
|
1638
2502
|
console.log(chalk.bold('Installing dependencies...'));
|
|
@@ -1674,6 +2538,9 @@ async function handleTemplate() {
|
|
|
1674
2538
|
// Config parse error is non-fatal
|
|
1675
2539
|
}
|
|
1676
2540
|
}
|
|
2541
|
+
// 5b. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
2542
|
+
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
2543
|
+
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
1677
2544
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
1678
2545
|
console.log(chalk.bold('Refreshing editor...'));
|
|
1679
2546
|
try {
|
|
@@ -1692,17 +2559,14 @@ async function handleTemplate() {
|
|
|
1692
2559
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
1693
2560
|
/**
|
|
1694
2561
|
* `codeyam editor sync`
|
|
1695
|
-
* Import scenarios from scenarios
|
|
2562
|
+
* Import scenarios from editor-scenarios/ files into the local database.
|
|
2563
|
+
* Falls back to legacy scenarios-manifest.json if no _metadata files found.
|
|
1696
2564
|
*/
|
|
1697
2565
|
async function handleSync() {
|
|
1698
2566
|
const root = getProjectRoot();
|
|
1699
|
-
const
|
|
1700
|
-
if (
|
|
1701
|
-
console.log(chalk.yellow('No
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
if (manifest.scenarios.length === 0) {
|
|
1705
|
-
console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
|
|
2567
|
+
const entries = scanScenarioFiles(root);
|
|
2568
|
+
if (entries.length === 0) {
|
|
2569
|
+
console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
|
|
1706
2570
|
return;
|
|
1707
2571
|
}
|
|
1708
2572
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
@@ -1726,13 +2590,12 @@ async function handleSync() {
|
|
|
1726
2590
|
const { project } = await requireBranchAndProject(projectSlug);
|
|
1727
2591
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
1728
2592
|
const db = getDatabase();
|
|
1729
|
-
// Fetch existing editor scenarios
|
|
1730
2593
|
const existingRows = await db
|
|
1731
2594
|
.selectFrom('editor_scenarios')
|
|
1732
2595
|
.select(['id', 'updated_at'])
|
|
1733
2596
|
.where('project_id', '=', project.id)
|
|
1734
2597
|
.execute();
|
|
1735
|
-
const result = await
|
|
2598
|
+
const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
|
|
1736
2599
|
await db
|
|
1737
2600
|
.insertInto('editor_scenarios')
|
|
1738
2601
|
.values(row)
|
|
@@ -1757,14 +2620,45 @@ async function handleSync() {
|
|
|
1757
2620
|
parts.push(`${result.skipped} unchanged`);
|
|
1758
2621
|
console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
|
|
1759
2622
|
}
|
|
2623
|
+
// Migrate legacy scenario formats after sync, then re-sync if anything was fixed
|
|
2624
|
+
try {
|
|
2625
|
+
const migrateResult = migrateScenarioFormats(root);
|
|
2626
|
+
if (migrateResult.fixed > 0) {
|
|
2627
|
+
console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
2628
|
+
// Re-sync so the DB reflects the corrected file metadata
|
|
2629
|
+
const refreshedRows = await db
|
|
2630
|
+
.selectFrom('editor_scenarios')
|
|
2631
|
+
.select(['id', 'updated_at'])
|
|
2632
|
+
.where('project_id', '=', project.id)
|
|
2633
|
+
.execute();
|
|
2634
|
+
await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
|
|
2635
|
+
await db
|
|
2636
|
+
.insertInto('editor_scenarios')
|
|
2637
|
+
.values(row)
|
|
2638
|
+
.execute();
|
|
2639
|
+
}, async (id, row) => {
|
|
2640
|
+
await db
|
|
2641
|
+
.updateTable('editor_scenarios')
|
|
2642
|
+
.set(row)
|
|
2643
|
+
.where('id', '=', id)
|
|
2644
|
+
.execute();
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
catch {
|
|
2649
|
+
// Non-fatal
|
|
2650
|
+
}
|
|
1760
2651
|
}
|
|
1761
2652
|
// ─── Verify Images subcommand ─────────────────────────────────────────
|
|
1762
2653
|
async function handleVerifyImages(jsonArg) {
|
|
1763
2654
|
const port = getServerPort();
|
|
1764
|
-
const {
|
|
2655
|
+
const { parseVerifyImagesInput, extractImageUrls, resolveImageUrl, verifyImageUrls, buildVerifyImagesReport, extractImageUrlsFromScenarioFiles, } = await import('../utils/editorImageVerifier.js');
|
|
1765
2656
|
let paths;
|
|
2657
|
+
let explicitImageUrls;
|
|
1766
2658
|
try {
|
|
1767
|
-
|
|
2659
|
+
const input = parseVerifyImagesInput(jsonArg);
|
|
2660
|
+
paths = input.paths;
|
|
2661
|
+
explicitImageUrls = input.imageUrls;
|
|
1768
2662
|
}
|
|
1769
2663
|
catch {
|
|
1770
2664
|
console.error(chalk.red('Error: Invalid JSON argument'));
|
|
@@ -1802,12 +2696,23 @@ async function handleVerifyImages(jsonArg) {
|
|
|
1802
2696
|
console.error(chalk.red(` ✗ ${pagePath} — ${err.message}`));
|
|
1803
2697
|
}
|
|
1804
2698
|
}
|
|
2699
|
+
// Add explicitly-provided image URLs (Claude passes these directly)
|
|
2700
|
+
for (const url of explicitImageUrls) {
|
|
2701
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
2702
|
+
}
|
|
2703
|
+
// Also scan scenario mock data files for image URLs (client-rendered pages)
|
|
2704
|
+
const scenariosDir = path.join(getProjectRoot(), '.codeyam', 'editor-scenarios');
|
|
2705
|
+
const { urls: scenarioUrls, filesScanned: scenarioFilesScanned } = extractImageUrlsFromScenarioFiles(scenariosDir);
|
|
2706
|
+
for (const url of scenarioUrls) {
|
|
2707
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
2708
|
+
}
|
|
1805
2709
|
const urlList = [...allUrls];
|
|
1806
2710
|
const results = urlList.length > 0 ? await verifyImageUrls(urlList) : [];
|
|
1807
2711
|
const report = buildVerifyImagesReport({
|
|
1808
2712
|
pagesChecked,
|
|
1809
2713
|
imageUrls: urlList,
|
|
1810
2714
|
results,
|
|
2715
|
+
scenarioFilesScanned,
|
|
1811
2716
|
});
|
|
1812
2717
|
console.log(JSON.stringify(report, null, 2));
|
|
1813
2718
|
if (report.failures.length > 0) {
|
|
@@ -1875,8 +2780,11 @@ function handleEditorDebug(args) {
|
|
|
1875
2780
|
11: printStep11,
|
|
1876
2781
|
12: printStep12,
|
|
1877
2782
|
13: printStep13,
|
|
2783
|
+
14: printStep14,
|
|
2784
|
+
15: printStep15,
|
|
2785
|
+
16: printStep16,
|
|
1878
2786
|
};
|
|
1879
|
-
for (let step = 1; step <=
|
|
2787
|
+
for (let step = 1; step <= 16; step++) {
|
|
1880
2788
|
const stepId = `step-${step}`;
|
|
1881
2789
|
if (!wants(stepId))
|
|
1882
2790
|
continue;
|
|
@@ -1955,8 +2863,8 @@ const editorCommand = {
|
|
|
1955
2863
|
describe: 'Editor mode guided workflow',
|
|
1956
2864
|
builder: (yargs) => {
|
|
1957
2865
|
const stepDescription = IS_INTERNAL_BUILD
|
|
1958
|
-
? 'Step number (1-
|
|
1959
|
-
: 'Step number (1-
|
|
2866
|
+
? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
|
|
2867
|
+
: 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
|
|
1960
2868
|
let builder = yargs
|
|
1961
2869
|
.positional('step', {
|
|
1962
2870
|
type: 'string',
|
|
@@ -1969,6 +2877,14 @@ const editorCommand = {
|
|
|
1969
2877
|
.option('feature', {
|
|
1970
2878
|
type: 'string',
|
|
1971
2879
|
describe: 'Feature name (required for step 2)',
|
|
2880
|
+
})
|
|
2881
|
+
.option('app-formats', {
|
|
2882
|
+
type: 'string',
|
|
2883
|
+
describe: 'Comma-separated app formats (mobile-responsive-web-app, desktop-app, mobile-app)',
|
|
2884
|
+
})
|
|
2885
|
+
.option('tech-stack', {
|
|
2886
|
+
type: 'string',
|
|
2887
|
+
describe: 'Selected tech stack ID (e.g., nextjs-prisma-sqlite)',
|
|
1972
2888
|
})
|
|
1973
2889
|
.option('prompt', {
|
|
1974
2890
|
type: 'string',
|
|
@@ -1984,7 +2900,7 @@ const editorCommand = {
|
|
|
1984
2900
|
builder = builder
|
|
1985
2901
|
.option('target', {
|
|
1986
2902
|
type: 'string',
|
|
1987
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
2903
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
|
|
1988
2904
|
})
|
|
1989
2905
|
.option('resume', {
|
|
1990
2906
|
type: 'boolean',
|
|
@@ -2018,7 +2934,13 @@ const editorCommand = {
|
|
|
2018
2934
|
process.exit(1);
|
|
2019
2935
|
}
|
|
2020
2936
|
if (result.data != null) {
|
|
2021
|
-
|
|
2937
|
+
const summary = formatApiSubcommandResult(argv.step, result.data);
|
|
2938
|
+
if (summary) {
|
|
2939
|
+
console.log(summary);
|
|
2940
|
+
}
|
|
2941
|
+
else {
|
|
2942
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
2943
|
+
}
|
|
2022
2944
|
}
|
|
2023
2945
|
}
|
|
2024
2946
|
catch (err) {
|
|
@@ -2053,6 +2975,11 @@ const editorCommand = {
|
|
|
2053
2975
|
await handleScenarios();
|
|
2054
2976
|
return;
|
|
2055
2977
|
}
|
|
2978
|
+
// Subcommand: codeyam editor scenario-coverage
|
|
2979
|
+
if (argv.step === 'scenario-coverage') {
|
|
2980
|
+
await handleScenarioCoverage();
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2056
2983
|
// Subcommand: codeyam editor change <feature>
|
|
2057
2984
|
if (argv.step === 'change') {
|
|
2058
2985
|
handleChange(argv.json || '');
|
|
@@ -2063,6 +2990,22 @@ const editorCommand = {
|
|
|
2063
2990
|
await handleVerifyImages(argv.json || '');
|
|
2064
2991
|
return;
|
|
2065
2992
|
}
|
|
2993
|
+
// Subcommand: codeyam editor validate-seed '{"products":[...]}'
|
|
2994
|
+
if (argv.step === 'validate-seed') {
|
|
2995
|
+
await handleValidateSeed(argv.json || '');
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
2999
|
+
if (argv.step === 'isolate') {
|
|
3000
|
+
const names = [];
|
|
3001
|
+
if (argv.json)
|
|
3002
|
+
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
3003
|
+
// Collect any extra positional args (yargs puts them in argv._)
|
|
3004
|
+
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
3005
|
+
names.push(...extras);
|
|
3006
|
+
handleIsolate(names);
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
2066
3009
|
// Subcommand: codeyam editor template — scaffold project from template
|
|
2067
3010
|
if (argv.step === 'template') {
|
|
2068
3011
|
await handleTemplate();
|
|
@@ -2089,9 +3032,9 @@ const editorCommand = {
|
|
|
2089
3032
|
}
|
|
2090
3033
|
else {
|
|
2091
3034
|
const state = readState(root);
|
|
2092
|
-
// Clear prompt file when feature is done (step
|
|
3035
|
+
// Clear prompt file when feature is done (step 16) so the hook
|
|
2093
3036
|
// can capture the next feature request from the user.
|
|
2094
|
-
if (state?.step ===
|
|
3037
|
+
if (state?.step === 16) {
|
|
2095
3038
|
clearEditorUserPrompt(root);
|
|
2096
3039
|
}
|
|
2097
3040
|
printCycleOverview(root, state);
|
|
@@ -2099,8 +3042,8 @@ const editorCommand = {
|
|
|
2099
3042
|
return;
|
|
2100
3043
|
}
|
|
2101
3044
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
2102
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
2103
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
3045
|
+
if (step != null && (isNaN(step) || step < 1 || step > 16)) {
|
|
3046
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
|
|
2104
3047
|
process.exit(1);
|
|
2105
3048
|
}
|
|
2106
3049
|
if (step == null) {
|
|
@@ -2133,9 +3076,42 @@ const editorCommand = {
|
|
|
2133
3076
|
return;
|
|
2134
3077
|
}
|
|
2135
3078
|
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
2136
|
-
//
|
|
2137
|
-
|
|
2138
|
-
|
|
3079
|
+
// Backfill _metadata into existing scenario files that predate the embedded metadata feature.
|
|
3080
|
+
// This reads metadata from the DB and writes it into files that don't have _metadata yet.
|
|
3081
|
+
try {
|
|
3082
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3083
|
+
const db = getDatabase();
|
|
3084
|
+
const dbScenarios = await db
|
|
3085
|
+
.selectFrom('editor_scenarios')
|
|
3086
|
+
.select([
|
|
3087
|
+
'id',
|
|
3088
|
+
'name',
|
|
3089
|
+
'description',
|
|
3090
|
+
'component_name',
|
|
3091
|
+
'component_path',
|
|
3092
|
+
'url',
|
|
3093
|
+
'type',
|
|
3094
|
+
'screenshot_path',
|
|
3095
|
+
'viewport_width',
|
|
3096
|
+
'viewport_height',
|
|
3097
|
+
'created_at',
|
|
3098
|
+
'updated_at',
|
|
3099
|
+
])
|
|
3100
|
+
.where('project_id', '=', project.id)
|
|
3101
|
+
.execute();
|
|
3102
|
+
if (dbScenarios.length > 0) {
|
|
3103
|
+
const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
|
|
3104
|
+
if (backfilled > 0) {
|
|
3105
|
+
console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
catch {
|
|
3110
|
+
// Non-fatal — backfill is best-effort
|
|
3111
|
+
}
|
|
3112
|
+
// Auto-sync scenario files (with _metadata) to database
|
|
3113
|
+
const scenarioEntries = scanScenarioFiles(projectRoot);
|
|
3114
|
+
if (scenarioEntries.length > 0) {
|
|
2139
3115
|
try {
|
|
2140
3116
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2141
3117
|
const db = getDatabase();
|
|
@@ -2144,7 +3120,7 @@ const editorCommand = {
|
|
|
2144
3120
|
.select(['id', 'updated_at'])
|
|
2145
3121
|
.where('project_id', '=', project.id)
|
|
2146
3122
|
.execute();
|
|
2147
|
-
const syncResult = await
|
|
3123
|
+
const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
|
|
2148
3124
|
await db
|
|
2149
3125
|
.insertInto('editor_scenarios')
|
|
2150
3126
|
.values(row)
|
|
@@ -2162,13 +3138,50 @@ const editorCommand = {
|
|
|
2162
3138
|
parts.push(`${syncResult.inserted} imported`);
|
|
2163
3139
|
if (syncResult.updated > 0)
|
|
2164
3140
|
parts.push(`${syncResult.updated} updated`);
|
|
2165
|
-
console.log(chalk.green(` Synced scenarios from
|
|
3141
|
+
console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
|
|
2166
3142
|
}
|
|
2167
3143
|
}
|
|
2168
3144
|
catch {
|
|
2169
3145
|
// Non-fatal — sync failure shouldn't block editor startup
|
|
2170
3146
|
}
|
|
2171
3147
|
}
|
|
3148
|
+
// Migrate legacy scenario formats: resolve null viewports, populate
|
|
3149
|
+
// dimensions arrays, and build screenshotPaths maps from single values.
|
|
3150
|
+
// If files were fixed, re-sync to update the database with corrected values.
|
|
3151
|
+
try {
|
|
3152
|
+
const migrateResult = migrateScenarioFormats(projectRoot);
|
|
3153
|
+
if (migrateResult.fixed > 0) {
|
|
3154
|
+
console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
3155
|
+
// Re-sync so the DB reflects the fixed file metadata
|
|
3156
|
+
try {
|
|
3157
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3158
|
+
const db = getDatabase();
|
|
3159
|
+
const rows = await db
|
|
3160
|
+
.selectFrom('editor_scenarios')
|
|
3161
|
+
.select(['id', 'updated_at'])
|
|
3162
|
+
.where('project_id', '=', project.id)
|
|
3163
|
+
.execute();
|
|
3164
|
+
await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
|
|
3165
|
+
await db
|
|
3166
|
+
.insertInto('editor_scenarios')
|
|
3167
|
+
.values(row)
|
|
3168
|
+
.execute();
|
|
3169
|
+
}, async (id, row) => {
|
|
3170
|
+
await db
|
|
3171
|
+
.updateTable('editor_scenarios')
|
|
3172
|
+
.set(row)
|
|
3173
|
+
.where('id', '=', id)
|
|
3174
|
+
.execute();
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
catch {
|
|
3178
|
+
// Non-fatal — DB re-sync failure shouldn't block startup
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
catch {
|
|
3183
|
+
// Non-fatal — migration failure shouldn't block editor startup
|
|
3184
|
+
}
|
|
2172
3185
|
// `codeyam editor` (no step) always implies editor mode.
|
|
2173
3186
|
// The empty-folder heuristic is no longer needed here — running this
|
|
2174
3187
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -2207,38 +3220,46 @@ const editorCommand = {
|
|
|
2207
3220
|
editorMode,
|
|
2208
3221
|
});
|
|
2209
3222
|
// Auto-finalize analyzer so codeyam analyze works
|
|
2210
|
-
if (editorMode
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
});
|
|
2224
|
-
execSync('npm run build', {
|
|
2225
|
-
cwd: templatePath,
|
|
2226
|
-
stdio: 'pipe',
|
|
2227
|
-
});
|
|
2228
|
-
fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
|
|
3223
|
+
if (editorMode) {
|
|
3224
|
+
const stepLabels = {
|
|
3225
|
+
'npm-install': 'Installing simulation dependencies...',
|
|
3226
|
+
'playwright-install': 'Installing browser (Chromium)...',
|
|
3227
|
+
build: 'Building simulation engine...',
|
|
3228
|
+
};
|
|
3229
|
+
const simProgress = new ProgressReporter();
|
|
3230
|
+
const finalization = ensureAnalyzerFinalized({
|
|
3231
|
+
onProgress: (step) => simProgress.start(stepLabels[step]),
|
|
3232
|
+
});
|
|
3233
|
+
if (finalization.errors.length > 0) {
|
|
3234
|
+
for (const err of finalization.errors) {
|
|
3235
|
+
simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
|
|
2229
3236
|
}
|
|
2230
3237
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
3238
|
+
else if (finalization.needed) {
|
|
3239
|
+
simProgress.succeed('Simulation engine ready');
|
|
2233
3240
|
}
|
|
2234
3241
|
}
|
|
2235
|
-
// Start background server
|
|
3242
|
+
// Start background server (handles killing existing servers internally)
|
|
3243
|
+
const editorPort = argv.port || 3111;
|
|
2236
3244
|
const { url } = await startBackgroundServer({
|
|
2237
|
-
port:
|
|
3245
|
+
port: editorPort,
|
|
2238
3246
|
rootPath: projectRoot,
|
|
2239
3247
|
project,
|
|
2240
3248
|
branch,
|
|
2241
3249
|
});
|
|
3250
|
+
// Build import graph on first startup if glossary exists but no entities yet
|
|
3251
|
+
const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
|
|
3252
|
+
if (fs.existsSync(glossaryPath)) {
|
|
3253
|
+
const entities = await loadEntities({});
|
|
3254
|
+
if (!entities || entities.length === 0) {
|
|
3255
|
+
try {
|
|
3256
|
+
await handleAnalyzeImports({ silent: true });
|
|
3257
|
+
}
|
|
3258
|
+
catch {
|
|
3259
|
+
// Non-fatal
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
2242
3263
|
console.log();
|
|
2243
3264
|
console.log(` Dashboard: ${url}`);
|
|
2244
3265
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -2266,11 +3287,27 @@ const editorCommand = {
|
|
|
2266
3287
|
return;
|
|
2267
3288
|
}
|
|
2268
3289
|
const state = readState(root);
|
|
3290
|
+
// Validate step transition — prevent skipping steps.
|
|
3291
|
+
// Exception: step 2 with --feature is always allowed because step 1's
|
|
3292
|
+
// instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
|
|
3293
|
+
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
3294
|
+
const skipValidation = step === 2 && argv.feature;
|
|
3295
|
+
if (!skipValidation) {
|
|
3296
|
+
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
3297
|
+
if (stepError) {
|
|
3298
|
+
console.error(chalk.red(`Error: ${stepError}`));
|
|
3299
|
+
process.exit(1);
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
2269
3302
|
switch (step) {
|
|
2270
3303
|
case 1: {
|
|
2271
3304
|
const feature = argv.feature || undefined;
|
|
3305
|
+
const appFormats = argv['app-formats']
|
|
3306
|
+
? argv['app-formats'].split(',').map((f) => f.trim())
|
|
3307
|
+
: undefined;
|
|
3308
|
+
const techStackId = argv['tech-stack'] || undefined;
|
|
2272
3309
|
const prompt = argv.prompt || undefined;
|
|
2273
|
-
printStep1(root, feature, prompt);
|
|
3310
|
+
printStep1(root, feature, { appFormats, techStackId }, prompt);
|
|
2274
3311
|
break;
|
|
2275
3312
|
}
|
|
2276
3313
|
case 2: {
|
|
@@ -2293,12 +3330,24 @@ const editorCommand = {
|
|
|
2293
3330
|
case 10:
|
|
2294
3331
|
case 11:
|
|
2295
3332
|
case 12:
|
|
2296
|
-
case 13:
|
|
3333
|
+
case 13:
|
|
3334
|
+
case 14:
|
|
3335
|
+
case 15:
|
|
3336
|
+
case 16: {
|
|
2297
3337
|
const feature = argv.feature || state?.feature;
|
|
2298
3338
|
if (!feature) {
|
|
2299
3339
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
2300
3340
|
process.exit(1);
|
|
2301
3341
|
}
|
|
3342
|
+
// Hard gate: steps 8+ require audit to have passed
|
|
3343
|
+
if (step >= 8) {
|
|
3344
|
+
const auditOk = await checkAuditGate();
|
|
3345
|
+
if (!auditOk) {
|
|
3346
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Run `codeyam editor audit` and fix all issues before proceeding.'));
|
|
3347
|
+
console.error(chalk.yellow('Every function and hook must have a test file. Every component must have scenarios.'));
|
|
3348
|
+
process.exit(1);
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
2302
3351
|
const stepFns = {
|
|
2303
3352
|
3: printStep3,
|
|
2304
3353
|
4: printStep4,
|
|
@@ -2311,6 +3360,9 @@ const editorCommand = {
|
|
|
2311
3360
|
11: printStep11,
|
|
2312
3361
|
12: printStep12,
|
|
2313
3362
|
13: printStep13,
|
|
3363
|
+
14: printStep14,
|
|
3364
|
+
15: printStep15,
|
|
3365
|
+
16: printStep16,
|
|
2314
3366
|
};
|
|
2315
3367
|
stepFns[step](root, feature);
|
|
2316
3368
|
break;
|