@codeyam/codeyam-cli 0.1.0-staging.a890816 → 0.1.0-staging.ad88eeb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +9 -9
- package/analyzer-template/packages/ai/package.json +1 -1
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +0 -33
- package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +13 -7
- package/analyzer-template/packages/analyze/src/lib/asts/index.ts +7 -2
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +0 -98
- package/analyzer-template/packages/aws/package.json +2 -2
- package/analyzer-template/packages/database/package.json +3 -3
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +20 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +4 -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 +20 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.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/github/package.json +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/__tests__/memory-scripts/filter-session.test.js +196 -0
- package/codeyam-cli/src/__tests__/memory-scripts/filter-session.test.js.map +1 -0
- package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js +114 -0
- package/codeyam-cli/src/__tests__/memory-scripts/read-json-field.test.js.map +1 -0
- package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js +149 -0
- package/codeyam-cli/src/__tests__/memory-scripts/ripgrep-fallback.test.js.map +1 -0
- package/codeyam-cli/src/commands/default.js +3 -46
- package/codeyam-cli/src/commands/default.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +1893 -215
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +6 -1
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/data/techStacks.js +82 -0
- package/codeyam-cli/src/data/techStacks.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js +134 -0
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js +127 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +635 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorCapture.test.js +93 -0
- package/codeyam-cli/src/utils/__tests__/editorCapture.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js +279 -0
- package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +121 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +294 -0
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +542 -0
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +393 -0
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorMockState.test.js +270 -0
- package/codeyam-cli/src/utils/__tests__/editorMockState.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js +217 -0
- package/codeyam-cli/src/utils/__tests__/editorPreloadHelpers.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +266 -0
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +107 -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 +221 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -0
- 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 +1737 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/git.editor.test.js +134 -0
- package/codeyam-cli/src/utils/__tests__/git.editor.test.js.map +1 -0
- 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 +101 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/project.test.js +65 -0
- package/codeyam-cli/src/utils/__tests__/project.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioMarkers.test.js +121 -0
- package/codeyam-cli/src/utils/__tests__/scenarioMarkers.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +246 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +25 -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/backgroundServer.js +2 -2
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/buildFlags.js +4 -0
- package/codeyam-cli/src/utils/buildFlags.js.map +1 -0
- package/codeyam-cli/src/utils/devServerState.js +71 -0
- package/codeyam-cli/src/utils/devServerState.js.map +1 -0
- package/codeyam-cli/src/utils/editorApi.js +73 -0
- package/codeyam-cli/src/utils/editorApi.js.map +1 -0
- package/codeyam-cli/src/utils/editorAudit.js +159 -0
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -0
- package/codeyam-cli/src/utils/editorCapture.js +102 -0
- package/codeyam-cli/src/utils/editorCapture.js.map +1 -0
- package/codeyam-cli/src/utils/editorDevServer.js +193 -0
- package/codeyam-cli/src/utils/editorDevServer.js.map +1 -0
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js +44 -0
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -0
- package/codeyam-cli/src/utils/editorImageVerifier.js +155 -0
- package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -0
- package/codeyam-cli/src/utils/editorJournal.js +225 -0
- package/codeyam-cli/src/utils/editorJournal.js.map +1 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +81 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -0
- package/codeyam-cli/src/utils/editorMockState.js +248 -0
- package/codeyam-cli/src/utils/editorMockState.js.map +1 -0
- package/codeyam-cli/src/utils/editorPreloadHelpers.js +135 -0
- package/codeyam-cli/src/utils/editorPreloadHelpers.js.map +1 -0
- package/codeyam-cli/src/utils/editorPreview.js +106 -0
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -0
- 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 +96 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -0
- 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 +347 -0
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +158 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -0
- package/codeyam-cli/src/utils/git.js +51 -0
- package/codeyam-cli/src/utils/git.js.map +1 -1
- package/codeyam-cli/src/utils/install-skills.js +28 -17
- package/codeyam-cli/src/utils/install-skills.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/project.js +15 -5
- package/codeyam-cli/src/utils/project.js.map +1 -1
- package/codeyam-cli/src/utils/scenarioMarkers.js +134 -0
- package/codeyam-cli/src/utils/scenarioMarkers.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +112 -0
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -0
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +46 -16
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/testRunner.js +1 -1
- package/codeyam-cli/src/utils/testRunner.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__/editorProxy.test.js +178 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/git.js +396 -0
- package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -0
- 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-B76aig_2.js → ScenarioViewer-0DY_NKil.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +1 -0
- 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/addon-canvas-DpzMmAy5.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/addon-fit-YJmn1quW.js +12 -0
- package/codeyam-cli/src/webserver/build/client/assets/addon-webgl-DI8QOUvO.js +58 -0
- 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-audit-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-diff-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-file-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-load-commit-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-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-Csi0_PMl.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor-BuT_Huj0.js +10 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-B7ztwLut.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-CrjR3zZW.js → entity._sha._-BF4oLwaE.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-D5rYBT5x.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.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-DdZcvjGh.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-BkWJ_UNc.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/index-yHOVb4rc.js +15 -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-b0f1372e.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-FweZHj5U.js → memory-Bl2rpw8u.js} +13 -10
- 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-DiRdBreB.js → root-B_X8HS1x.js} +18 -18
- 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-CrAK28Bc.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/xterm-BqvuqXEL.js +27 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-BzAbACSx.js → index-CbF6h3dj.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DRFwTJqO.js +367 -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 +383 -50
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/scripts/codeyam-preload.mjs +414 -0
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +94 -4
- package/codeyam-cli/src/webserver/server.js +93 -12
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +65 -112
- 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/gitignore +15 -0
- package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +26 -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 +35 -0
- package/codeyam-cli/templates/editor-step-hook.py +95 -9
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -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 +37 -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 +112 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/app/codeyam-isolate/layout.tsx +12 -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 +21 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +4 -1
- 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-sqlite/vitest.config.ts +13 -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 +36 -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/{codeyam-dev-mode.md → skills/codeyam-dev-mode/SKILL.md} +2 -2
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +145 -0
- package/codeyam-cli/templates/{codeyam-memory.md → skills/codeyam-memory/SKILL.md} +215 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/deprecated-prompt.md +100 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/detect-deprecated-patterns.mjs +139 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/find-exports.mjs +52 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/holistic-analysis/misleading-api-prompt.md +117 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/read-json-field.mjs +61 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/lib/ripgrep-fallback.mjs +155 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/analyze-prompt.md +46 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/cleanup.mjs +13 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/filter-session.mjs +95 -0
- package/codeyam-cli/templates/skills/codeyam-memory/scripts/session-mining/preprocess.mjs +160 -0
- package/package.json +15 -10
- package/packages/ai/src/lib/generateExecutionFlows.js +0 -11
- package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
- package/packages/analyze/src/lib/ProjectAnalyzer.js +10 -4
- package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
- package/packages/analyze/src/lib/asts/index.js +4 -2
- package/packages/analyze/src/lib/asts/index.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +0 -40
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +20 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/types/src/enums/ProjectFramework.js +2 -0
- package/packages/types/src/enums/ProjectFramework.js.map +1 -1
- package/scripts/npm-post-install.cjs +34 -0
- package/codeyam-cli/src/webserver/build/client/assets/Terminal-CcG8YTLx.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/addon-fit-CUXOrorO.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-CMT1jU2q.js +0 -21
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-BiM6z3Do.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-W_IGJ2Kd.js +0 -7
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-D6SEzMCu.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-C28BiQzt.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/git-CFCTYk9I.js +0 -15
- package/codeyam-cli/src/webserver/build/client/assets/globals-BZB_H1w2.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-8daa4147.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-ByhSyh0W.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/xterm-DMSzMhqy.js +0 -9
- package/codeyam-cli/src/webserver/build/server/assets/server-build-OdUocH6P.js +0 -362
- package/codeyam-cli/templates/codeyam-editor.md +0 -68
- package/scripts/finalize-analyzer.cjs +0 -13
- /package/codeyam-cli/templates/{codeyam-diagnose.md → commands/codeyam-diagnose.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-debug.md → skills/codeyam-debug/SKILL.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-new-rule.md → skills/codeyam-new-rule/SKILL.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-setup.md → skills/codeyam-setup/SKILL.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-sim.md → skills/codeyam-sim/SKILL.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-test.md → skills/codeyam-test/SKILL.md} +0 -0
- /package/codeyam-cli/templates/{codeyam-verify.md → skills/codeyam-verify/SKILL.md} +0 -0
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import chalk from 'chalk';
|
|
5
6
|
import { runAnalysisForEntities } from "../utils/analysisRunner.js";
|
|
6
|
-
import { ProgressReporter } from "../utils/progress.js";
|
|
7
|
-
import { initializeEnvironment } from "../utils/database.js";
|
|
8
|
-
import { loadEntities,
|
|
7
|
+
import { error as errorLog, ProgressReporter, withoutSpinner, } from "../utils/progress.js";
|
|
8
|
+
import { initializeEnvironment, requireBranchAndProject, testEnvironment, } from "../utils/database.js";
|
|
9
|
+
import { loadAnalyses, loadEntities, updateProjectMetadata, } from "../../../packages/database/index.js";
|
|
10
|
+
import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
|
|
11
|
+
import { startBackgroundServer } from "../utils/backgroundServer.js";
|
|
12
|
+
import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
13
|
+
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
|
+
import { getAnalyzerTemplatePath, isAnalyzerFinalized, } from "../utils/analyzer.js";
|
|
15
|
+
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
|
+
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
|
+
import initCommand from "./init.js";
|
|
18
|
+
import { readManifest, syncManifestToDatabase, } from "../utils/scenariosManifest.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
21
|
+
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
|
+
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
9
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
24
|
const __dirname = path.dirname(__filename);
|
|
11
25
|
const STEP_LABELS = {
|
|
@@ -19,7 +33,9 @@ const STEP_LABELS = {
|
|
|
19
33
|
8: 'App Scenarios',
|
|
20
34
|
9: 'User Scenarios',
|
|
21
35
|
10: 'Verify',
|
|
22
|
-
11: '
|
|
36
|
+
11: 'Journal',
|
|
37
|
+
12: 'Review',
|
|
38
|
+
13: 'Present',
|
|
23
39
|
};
|
|
24
40
|
/**
|
|
25
41
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
@@ -76,15 +92,11 @@ function writeState(root, state) {
|
|
|
76
92
|
}
|
|
77
93
|
/**
|
|
78
94
|
* Clear the editor state (for starting a new feature).
|
|
95
|
+
* Does NOT clear the user prompt file — that's handled separately
|
|
96
|
+
* via clearEditorUserPrompt when the previous feature commits.
|
|
79
97
|
*/
|
|
80
98
|
function clearState(root) {
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
fs.unlinkSync(statePath);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// File doesn't exist, that's fine
|
|
87
|
-
}
|
|
99
|
+
clearEditorState(root);
|
|
88
100
|
}
|
|
89
101
|
/**
|
|
90
102
|
* Check if a project has been scaffolded (package.json exists).
|
|
@@ -119,13 +131,13 @@ function stepHeader(step, title, feature) {
|
|
|
119
131
|
console.log();
|
|
120
132
|
}
|
|
121
133
|
/**
|
|
122
|
-
* Print a colored progress tracker showing all
|
|
134
|
+
* Print a colored progress tracker showing all 13 steps.
|
|
123
135
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
124
136
|
*/
|
|
125
137
|
function printProgressTracker(current) {
|
|
126
138
|
console.log();
|
|
127
139
|
console.log(chalk.dim('┌─────────────────────────────────────┐'));
|
|
128
|
-
for (let i = 1; i <=
|
|
140
|
+
for (let i = 1; i <= 13; i++) {
|
|
129
141
|
const label = STEP_LABELS[i];
|
|
130
142
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
131
143
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -163,7 +175,7 @@ function stopGate(current, opts) {
|
|
|
163
175
|
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
164
176
|
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
165
177
|
console.log();
|
|
166
|
-
if (current <
|
|
178
|
+
if (current < 13) {
|
|
167
179
|
console.log(chalk.green('When done, run: ') +
|
|
168
180
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
169
181
|
}
|
|
@@ -174,6 +186,218 @@ function stopGate(current, opts) {
|
|
|
174
186
|
}
|
|
175
187
|
console.log();
|
|
176
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Print a RESUMING header with step-specific investigation instructions.
|
|
191
|
+
* Called when a step is re-entered (prevState.step === current step).
|
|
192
|
+
*/
|
|
193
|
+
function printResumptionHeader(step) {
|
|
194
|
+
const port = getServerPort();
|
|
195
|
+
const checks = {
|
|
196
|
+
1: [
|
|
197
|
+
'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
198
|
+
],
|
|
199
|
+
2: ['Check if project files already exist:\n `ls package.json src/`'],
|
|
200
|
+
3: ['This is a confirmation step — just re-present to user'],
|
|
201
|
+
4: [
|
|
202
|
+
'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
203
|
+
],
|
|
204
|
+
5: [
|
|
205
|
+
'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
|
|
206
|
+
],
|
|
207
|
+
6: [
|
|
208
|
+
'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
|
|
209
|
+
],
|
|
210
|
+
7: ['Check if isolation routes and registered scenarios already exist'],
|
|
211
|
+
8: ['Check existing scenarios:\n `codeyam editor scenarios`'],
|
|
212
|
+
9: [
|
|
213
|
+
'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
|
|
214
|
+
],
|
|
215
|
+
10: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
216
|
+
11: [
|
|
217
|
+
'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
|
|
218
|
+
'If an entry exists, use PATCH to update it — do NOT create a new one',
|
|
219
|
+
],
|
|
220
|
+
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
221
|
+
13: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
222
|
+
};
|
|
223
|
+
const label = STEP_LABELS[step] || 'Unknown';
|
|
224
|
+
console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
|
|
225
|
+
console.log(chalk.yellow('This step was already started. Before repeating any actions, investigate:'));
|
|
226
|
+
const items = checks[step] || [];
|
|
227
|
+
for (const item of items) {
|
|
228
|
+
checkbox(item);
|
|
229
|
+
}
|
|
230
|
+
console.log();
|
|
231
|
+
}
|
|
232
|
+
function captureOutput(fn) {
|
|
233
|
+
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
234
|
+
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
235
|
+
const chunks = [];
|
|
236
|
+
const captureWrite = (chunk, encoding, cb) => {
|
|
237
|
+
const text = typeof chunk === 'string'
|
|
238
|
+
? chunk
|
|
239
|
+
: chunk instanceof Buffer
|
|
240
|
+
? chunk.toString(typeof encoding === 'string' ? encoding : undefined)
|
|
241
|
+
: String(chunk);
|
|
242
|
+
chunks.push(text);
|
|
243
|
+
if (typeof encoding === 'function') {
|
|
244
|
+
encoding();
|
|
245
|
+
}
|
|
246
|
+
if (typeof cb === 'function') {
|
|
247
|
+
cb();
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
};
|
|
251
|
+
process.stdout.write = captureWrite;
|
|
252
|
+
process.stderr.write = captureWrite;
|
|
253
|
+
try {
|
|
254
|
+
fn();
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
process.stdout.write = stdoutWrite;
|
|
258
|
+
process.stderr.write = stderrWrite;
|
|
259
|
+
}
|
|
260
|
+
return chunks.join('');
|
|
261
|
+
}
|
|
262
|
+
function withTempRoot(hasProject, fn) {
|
|
263
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'codeyam-editor-debug-'));
|
|
264
|
+
if (hasProject) {
|
|
265
|
+
fs.writeFileSync(path.join(root, 'package.json'), '{"name":"codeyam-editor-debug","private":true}', 'utf8');
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
return fn(root);
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
try {
|
|
272
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Best-effort cleanup
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function makeState(step, feature) {
|
|
280
|
+
const now = new Date().toISOString();
|
|
281
|
+
return {
|
|
282
|
+
feature,
|
|
283
|
+
step,
|
|
284
|
+
label: STEP_LABELS[step],
|
|
285
|
+
startedAt: now,
|
|
286
|
+
featureStartedAt: now,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function normalizeDebugTarget(raw) {
|
|
290
|
+
const value = raw.trim().toLowerCase();
|
|
291
|
+
if (!value)
|
|
292
|
+
return '';
|
|
293
|
+
if (value === 'setup')
|
|
294
|
+
return 'setup';
|
|
295
|
+
if (value === 'overview')
|
|
296
|
+
return 'overview';
|
|
297
|
+
if (value === 'overview-with-state' || value === 'overview-state') {
|
|
298
|
+
return 'overview-with-state';
|
|
299
|
+
}
|
|
300
|
+
if (/^\d+$/.test(value)) {
|
|
301
|
+
return `step-${value}`;
|
|
302
|
+
}
|
|
303
|
+
if (/^step-?\d+$/.test(value)) {
|
|
304
|
+
const num = value.replace('step', '').replace('-', '');
|
|
305
|
+
return `step-${num}`;
|
|
306
|
+
}
|
|
307
|
+
return value;
|
|
308
|
+
}
|
|
309
|
+
function parseDebugTargets(target) {
|
|
310
|
+
if (!target || target.trim().toLowerCase() === 'all')
|
|
311
|
+
return null;
|
|
312
|
+
const rawTargets = target
|
|
313
|
+
.split(',')
|
|
314
|
+
.map((t) => normalizeDebugTarget(t))
|
|
315
|
+
.filter(Boolean);
|
|
316
|
+
const valid = new Set();
|
|
317
|
+
for (const entry of rawTargets) {
|
|
318
|
+
if (entry === 'setup' ||
|
|
319
|
+
entry === 'overview' ||
|
|
320
|
+
entry === 'overview-with-state') {
|
|
321
|
+
valid.add(entry);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (entry.startsWith('step-')) {
|
|
325
|
+
const num = parseInt(entry.replace('step-', ''), 10);
|
|
326
|
+
if (!isNaN(num) && num >= 1 && num <= 13) {
|
|
327
|
+
valid.add(`step-${num}`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`Invalid debug target: "${entry}"`);
|
|
332
|
+
}
|
|
333
|
+
return valid;
|
|
334
|
+
}
|
|
335
|
+
function writeContextSnapshot(root, outDir) {
|
|
336
|
+
const contextDir = path.join(outDir, 'context');
|
|
337
|
+
fs.mkdirSync(contextDir, { recursive: true });
|
|
338
|
+
const entries = [];
|
|
339
|
+
const skillPath = path.join(root, '.claude', 'skills', 'codeyam-editor', 'SKILL.md');
|
|
340
|
+
const skillFallback = path.join(__dirname, '..', '..', 'templates', 'codeyam-editor.md');
|
|
341
|
+
const skillSource = fs.existsSync(skillPath) ? skillPath : skillFallback;
|
|
342
|
+
const skillDest = path.join(contextDir, 'codeyam-editor-skill.md');
|
|
343
|
+
fs.copyFileSync(skillSource, skillDest);
|
|
344
|
+
entries.push({
|
|
345
|
+
label: 'codeyam-editor skill',
|
|
346
|
+
source: skillSource,
|
|
347
|
+
file: path.relative(outDir, skillDest),
|
|
348
|
+
status: fs.existsSync(skillPath) ? 'installed' : 'template',
|
|
349
|
+
});
|
|
350
|
+
const claudePath = path.join(root, 'CLAUDE.md');
|
|
351
|
+
if (fs.existsSync(claudePath)) {
|
|
352
|
+
const dest = path.join(contextDir, 'CLAUDE.md');
|
|
353
|
+
fs.copyFileSync(claudePath, dest);
|
|
354
|
+
entries.push({
|
|
355
|
+
label: 'CLAUDE.md',
|
|
356
|
+
source: claudePath,
|
|
357
|
+
file: path.relative(outDir, dest),
|
|
358
|
+
status: 'project',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const fallback = path.join(__dirname, '..', '..', 'templates', 'codeyam-editor-claude.md');
|
|
363
|
+
if (fs.existsSync(fallback)) {
|
|
364
|
+
const dest = path.join(contextDir, 'CLAUDE.md');
|
|
365
|
+
fs.copyFileSync(fallback, dest);
|
|
366
|
+
entries.push({
|
|
367
|
+
label: 'CLAUDE.md',
|
|
368
|
+
source: fallback,
|
|
369
|
+
file: path.relative(outDir, dest),
|
|
370
|
+
status: 'template',
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
entries.push({
|
|
375
|
+
label: 'CLAUDE.md',
|
|
376
|
+
file: path.relative(outDir, path.join(contextDir, 'CLAUDE.md')),
|
|
377
|
+
status: 'missing',
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const contextPath = path.join(root, '.codeyam', 'editor-mode-context.md');
|
|
382
|
+
if (fs.existsSync(contextPath)) {
|
|
383
|
+
const dest = path.join(contextDir, 'editor-mode-context.md');
|
|
384
|
+
fs.copyFileSync(contextPath, dest);
|
|
385
|
+
entries.push({
|
|
386
|
+
label: 'editor-mode-context.md',
|
|
387
|
+
source: contextPath,
|
|
388
|
+
file: path.relative(outDir, dest),
|
|
389
|
+
status: 'project',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
entries.push({
|
|
394
|
+
label: 'editor-mode-context.md',
|
|
395
|
+
file: path.relative(outDir, path.join(contextDir, 'editor-mode-context.md')),
|
|
396
|
+
status: 'missing',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return entries;
|
|
400
|
+
}
|
|
177
401
|
// ─── Setup (no args, no project) ──────────────────────────────────────
|
|
178
402
|
function printSetup(root) {
|
|
179
403
|
const port = getServerPort();
|
|
@@ -187,12 +411,68 @@ function printSetup(root) {
|
|
|
187
411
|
checkbox('Read `.codeyam/editor-mode-context.md` for session state');
|
|
188
412
|
checkbox('Ask the user what they want to build');
|
|
189
413
|
console.log();
|
|
414
|
+
// ── App Format Selection ───────────────────────────────────────────
|
|
415
|
+
console.log(chalk.bold('App Format Selection:'));
|
|
416
|
+
console.log(chalk.dim(' After the user describes their project, ask which app formats they want to target.'));
|
|
417
|
+
console.log(chalk.dim(' Use AskUserQuestion with CHECKBOXES (multiple selections allowed):'));
|
|
418
|
+
console.log();
|
|
419
|
+
for (const format of APP_FORMATS) {
|
|
420
|
+
console.log(chalk.yellow(` [ ] ${format.label}`) +
|
|
421
|
+
chalk.dim(` — ${format.description}`));
|
|
422
|
+
}
|
|
423
|
+
console.log();
|
|
424
|
+
// ── Tech Stack Selection ───────────────────────────────────────────
|
|
425
|
+
console.log(chalk.bold('Tech Stack Selection:'));
|
|
426
|
+
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
427
|
+
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
428
|
+
console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
|
|
429
|
+
console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
|
|
430
|
+
console.log();
|
|
431
|
+
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
432
|
+
for (const stack of TECH_STACKS) {
|
|
433
|
+
const recommended = stack.recommended ? chalk.green(' (Recommended)') : '';
|
|
434
|
+
const formatLabels = stack.supportedFormats
|
|
435
|
+
.map((f) => APP_FORMATS.find((af) => af.id === f)?.label)
|
|
436
|
+
.join(', ');
|
|
437
|
+
console.log(chalk.bold.yellow(` ${stack.name}`) +
|
|
438
|
+
recommended +
|
|
439
|
+
chalk.dim(` [id: ${stack.id}]`));
|
|
440
|
+
console.log(chalk.dim(` ${stack.description}`));
|
|
441
|
+
console.log(chalk.dim(' Technologies:'));
|
|
442
|
+
for (const tech of stack.technologies) {
|
|
443
|
+
console.log(chalk.dim(` - ${tech}`));
|
|
444
|
+
}
|
|
445
|
+
console.log(chalk.dim(` Supports: ${formatLabels}`));
|
|
446
|
+
console.log();
|
|
447
|
+
}
|
|
448
|
+
console.log(chalk.dim(' If NO stacks match the selected formats, tell the user that support for that format is coming soon'));
|
|
449
|
+
console.log(chalk.dim(' and suggest they pick a supported format for now.'));
|
|
450
|
+
console.log();
|
|
451
|
+
// ── Default Screen Size Selection ──────────────────────────────────
|
|
452
|
+
console.log(chalk.bold('Default Screen Size:'));
|
|
453
|
+
console.log(chalk.dim(' After selecting a tech stack, ask the user to choose the default screen size for previews and captures.'));
|
|
454
|
+
console.log(chalk.dim(' Use AskUserQuestion with RADIO buttons (single selection):'));
|
|
455
|
+
console.log();
|
|
456
|
+
console.log(chalk.yellow(' ( ) Desktop') + chalk.dim(' — 1440 × 900'));
|
|
457
|
+
console.log(chalk.yellow(' ( ) Laptop') + chalk.dim(' — 1024 × 768'));
|
|
458
|
+
console.log(chalk.yellow(' ( ) Tablet') + chalk.dim(' — 768 × 1024'));
|
|
459
|
+
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
460
|
+
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
461
|
+
console.log();
|
|
462
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-app/mobile-responsive-web-app → Mobile, chrome-extension → Custom (400×600), web-app/desktop-app → Desktop.'));
|
|
463
|
+
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
464
|
+
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}}\''));
|
|
465
|
+
console.log();
|
|
190
466
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
191
467
|
console.log();
|
|
192
|
-
console.log(chalk.red('Ask the user what they want to build, then run:'));
|
|
468
|
+
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:'));
|
|
193
469
|
console.log();
|
|
194
|
-
console.log(chalk.green(' ') +
|
|
470
|
+
console.log(chalk.green(' ') +
|
|
471
|
+
chalk.bold('codeyam editor 1 --feature "Feature Name" --app-formats "FORMAT_IDS" --tech-stack "STACK_ID" --prompt "the user\'s original message"'));
|
|
195
472
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what the user described.'));
|
|
473
|
+
console.log(chalk.dim(' Replace FORMAT_IDS with the comma-separated format IDs the user selected (e.g., "chrome-extension" or "web-app,mobile-responsive-web-app").'));
|
|
474
|
+
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").'));
|
|
475
|
+
console.log(chalk.dim(" Pass --prompt with the user's exact original request so it appears in the journal."));
|
|
196
476
|
console.log(chalk.dim(' Step 1 will guide you through planning and getting user confirmation before any code is written.'));
|
|
197
477
|
console.log();
|
|
198
478
|
}
|
|
@@ -210,9 +490,13 @@ function printCycleOverview(root, state) {
|
|
|
210
490
|
console.log(chalk.dim('Or run ') +
|
|
211
491
|
chalk.bold('codeyam editor 1') +
|
|
212
492
|
chalk.dim(' to start a new feature'));
|
|
493
|
+
console.log();
|
|
494
|
+
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
495
|
+
chalk.bold('codeyam editor change'));
|
|
496
|
+
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
213
497
|
}
|
|
214
498
|
else {
|
|
215
|
-
console.log('Each feature follows
|
|
499
|
+
console.log('Each feature follows 13 steps. You MUST run each command in order:');
|
|
216
500
|
console.log();
|
|
217
501
|
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
218
502
|
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
@@ -224,15 +508,23 @@ function printCycleOverview(root, state) {
|
|
|
224
508
|
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
|
|
225
509
|
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
|
|
226
510
|
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
|
|
227
|
-
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('
|
|
511
|
+
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
512
|
+
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
513
|
+
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
228
514
|
console.log();
|
|
229
515
|
console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
|
|
516
|
+
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
230
517
|
}
|
|
231
518
|
console.log();
|
|
232
519
|
}
|
|
233
520
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
234
|
-
function printStep1(root, feature) {
|
|
235
|
-
|
|
521
|
+
function printStep1(root, feature, options, userPrompt) {
|
|
522
|
+
const port = getServerPort();
|
|
523
|
+
const prevState = readState(root);
|
|
524
|
+
const isResuming = prevState?.step === 1;
|
|
525
|
+
if (!isResuming) {
|
|
526
|
+
clearState(root);
|
|
527
|
+
}
|
|
236
528
|
// If feature is provided, save initial state so step 2 can pick it up
|
|
237
529
|
if (feature) {
|
|
238
530
|
const now = new Date().toISOString();
|
|
@@ -240,12 +532,23 @@ function printStep1(root, feature) {
|
|
|
240
532
|
feature,
|
|
241
533
|
step: 1,
|
|
242
534
|
label: STEP_LABELS[1],
|
|
243
|
-
startedAt: now,
|
|
244
|
-
featureStartedAt: now,
|
|
535
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
536
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
537
|
+
appFormats: options?.appFormats || prevState?.appFormats,
|
|
538
|
+
techStackId: options?.techStackId || prevState?.techStackId,
|
|
245
539
|
});
|
|
246
540
|
}
|
|
541
|
+
// Save the user's original prompt to a separate file
|
|
542
|
+
if (userPrompt) {
|
|
543
|
+
const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
|
|
544
|
+
fs.mkdirSync(path.dirname(promptPath), { recursive: true });
|
|
545
|
+
fs.writeFileSync(promptPath, userPrompt, 'utf8');
|
|
546
|
+
}
|
|
247
547
|
logEvent(root, 'step', { step: 1, label: 'Plan', feature });
|
|
248
548
|
stepHeader(1, 'Plan', feature);
|
|
549
|
+
if (isResuming) {
|
|
550
|
+
printResumptionHeader(1);
|
|
551
|
+
}
|
|
249
552
|
console.log('Plan the feature before writing ANY code.');
|
|
250
553
|
console.log();
|
|
251
554
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -254,15 +557,19 @@ function printStep1(root, feature) {
|
|
|
254
557
|
checkbox('Ask clarifying questions using `AskUserQuestion` with selectable options');
|
|
255
558
|
console.log(chalk.dim(' Use AskUserQuestion for EVERY clarifying question — give 2-4 concrete options the user can pick from.'));
|
|
256
559
|
console.log(chalk.dim(' Focus on what the USER will see and do, not on databases, APIs, or components.'));
|
|
560
|
+
console.log(chalk.dim(' Do NOT ask about tech stack, frameworks, libraries, or implementation details — only ask about user-facing choices.'));
|
|
257
561
|
console.log(chalk.dim(' Good: "What should happen when there are no results?" → options: "Show empty state message", "Show suggestions"'));
|
|
258
562
|
console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
|
|
259
563
|
console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
|
|
260
564
|
checkbox("Summarize what you'll build in plain language the user can verify against their vision");
|
|
261
|
-
|
|
262
|
-
console.log(chalk.
|
|
263
|
-
console.log(chalk.
|
|
264
|
-
|
|
265
|
-
console.log(
|
|
565
|
+
checkbox('Set the project title and description for the App tab:');
|
|
566
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
567
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
568
|
+
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
569
|
+
console.log();
|
|
570
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
571
|
+
console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
|
|
572
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
266
573
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
267
574
|
console.log();
|
|
268
575
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
@@ -288,82 +595,68 @@ function printStep1(root, feature) {
|
|
|
288
595
|
function printStep2(root, feature) {
|
|
289
596
|
const port = getServerPort();
|
|
290
597
|
const projectExists = hasProject(root);
|
|
598
|
+
const prevState = readState(root);
|
|
599
|
+
const isResuming = prevState?.step === 2;
|
|
291
600
|
const now = new Date().toISOString();
|
|
292
601
|
writeState(root, {
|
|
293
602
|
feature,
|
|
294
603
|
step: 2,
|
|
295
604
|
label: STEP_LABELS[2],
|
|
296
|
-
startedAt: now,
|
|
297
|
-
featureStartedAt: now,
|
|
605
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
606
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
607
|
+
appFormats: prevState?.appFormats,
|
|
608
|
+
techStackId: prevState?.techStackId,
|
|
298
609
|
});
|
|
299
610
|
logEvent(root, 'step', { step: 2, label: 'Prototype', feature });
|
|
300
611
|
stepHeader(2, 'Prototype', feature);
|
|
612
|
+
if (isResuming) {
|
|
613
|
+
printResumptionHeader(2);
|
|
614
|
+
}
|
|
301
615
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
302
616
|
console.log();
|
|
303
617
|
// If no project exists yet, include scaffolding instructions first
|
|
304
618
|
if (!projectExists) {
|
|
305
|
-
const templateDir = path.join(__dirname, '..', '..', 'templates', 'nextjs-prisma-sqlite');
|
|
306
|
-
const hasTemplate = fs.existsSync(templateDir);
|
|
307
619
|
console.log(chalk.bold('Scaffold the project:'));
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
console.log(chalk.dim(' Copy template (Next.js + Prisma 7 + SQLite, pre-configured):'));
|
|
312
|
-
console.log(chalk.dim(` cp -r ${templateDir}/* .`));
|
|
313
|
-
console.log(chalk.dim(` cp ${templateDir}/.env .`));
|
|
314
|
-
console.log(chalk.dim(` cp ${templateDir}/gitignore .gitignore`));
|
|
315
|
-
console.log(chalk.dim(' npm install'));
|
|
316
|
-
console.log();
|
|
317
|
-
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
318
|
-
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
319
|
-
console.log();
|
|
320
|
-
checkbox('Push schema and seed the database');
|
|
321
|
-
console.log(chalk.dim(' npm run db:push'));
|
|
322
|
-
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
323
|
-
console.log(chalk.dim(' npm run db:seed'));
|
|
324
|
-
console.log();
|
|
325
|
-
console.log(chalk.dim(' See PRISMA_SETUP.md for Prisma patterns and important warnings.'));
|
|
326
|
-
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
327
|
-
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
checkbox('Scaffold the project (Next.js recommended) using temp-dir-rsync pattern');
|
|
331
|
-
console.log();
|
|
332
|
-
console.log(chalk.dim(' Scaffolding pattern (avoids .claude/ conflicts):'));
|
|
333
|
-
console.log(chalk.dim(' SCAFFOLD_DIR="/tmp/codeyam-scaffold-$$" && mkdir -p "$SCAFFOLD_DIR"'));
|
|
334
|
-
console.log(chalk.dim(' npx create-next-app@latest "$SCAFFOLD_DIR" --typescript --tailwind --eslint \\'));
|
|
335
|
-
console.log(chalk.dim(' --app --no-src-dir --no-import-alias --no-react-compiler --turbopack'));
|
|
336
|
-
console.log(chalk.dim(' rsync -a --exclude=\'.git\' "$SCAFFOLD_DIR/" . && rm -rf "$SCAFFOLD_DIR"'));
|
|
337
|
-
console.log();
|
|
338
|
-
checkbox('Set up Prisma + SQLite database');
|
|
339
|
-
console.log(chalk.dim(' npm install prisma @prisma/client @prisma/adapter-better-sqlite3 better-sqlite3'));
|
|
340
|
-
console.log(chalk.dim(' npm install -D @types/better-sqlite3'));
|
|
341
|
-
console.log(chalk.dim(' npx prisma init --datasource-provider sqlite'));
|
|
342
|
-
console.log(chalk.dim(' # Define schema, then: npx prisma db push'));
|
|
343
|
-
}
|
|
620
|
+
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
621
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
622
|
+
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
344
623
|
console.log();
|
|
345
|
-
checkbox('
|
|
346
|
-
console.log(chalk.dim(
|
|
624
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
625
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
347
626
|
console.log();
|
|
348
|
-
checkbox('
|
|
349
|
-
console.log(chalk.dim('
|
|
627
|
+
checkbox('Push schema and seed the database');
|
|
628
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
629
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
630
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
631
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
632
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
350
633
|
console.log();
|
|
351
|
-
|
|
352
|
-
console.log(chalk.dim('
|
|
353
|
-
console.log(chalk.dim('
|
|
354
|
-
console.log();
|
|
355
|
-
checkbox(`Notify CodeYam: \`curl -s -X POST http://localhost:${port}/api/editor-refresh\``);
|
|
634
|
+
console.log(chalk.dim(' See PRISMA_SETUP.md for Prisma patterns and important warnings.'));
|
|
635
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
636
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
356
637
|
console.log();
|
|
357
638
|
console.log(chalk.bold('Build the feature:'));
|
|
358
639
|
}
|
|
359
640
|
console.log(chalk.bold('Checklist:'));
|
|
360
|
-
checkbox('Create
|
|
641
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
361
642
|
checkbox('Seed the database with demo data');
|
|
362
|
-
checkbox('
|
|
643
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
644
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
645
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
646
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
363
647
|
checkbox('Verify the dev server shows the changes');
|
|
648
|
+
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
649
|
+
console.log();
|
|
650
|
+
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
651
|
+
console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
|
|
652
|
+
console.log(chalk.cyan(` codeyam editor preview`));
|
|
653
|
+
console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
|
|
654
|
+
console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
|
|
655
|
+
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
656
|
+
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1"}'`));
|
|
364
657
|
console.log();
|
|
365
658
|
console.log(chalk.bold('Verify the dev server:'));
|
|
366
|
-
console.log(chalk.dim(` # Get dev server URL:
|
|
659
|
+
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
367
660
|
console.log(chalk.dim(' # Check page loads: curl -s -o /dev/null -w "%{http_code}" http://localhost:<dev-port>'));
|
|
368
661
|
console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
|
|
369
662
|
console.log();
|
|
@@ -371,7 +664,10 @@ function printStep2(root, feature) {
|
|
|
371
664
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
372
665
|
checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
|
|
373
666
|
checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
|
|
374
|
-
checkbox('Check for broken images:
|
|
667
|
+
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
668
|
+
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
669
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
670
|
+
console.log(chalk.dim(' Fix or replace any broken image URLs in the seed data before proceeding.'));
|
|
375
671
|
checkbox('Check the dev server terminal output for runtime errors (missing modules, failed imports)');
|
|
376
672
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
377
673
|
console.log();
|
|
@@ -382,32 +678,48 @@ function printStep2(root, feature) {
|
|
|
382
678
|
function printStep3(root, feature) {
|
|
383
679
|
const port = getServerPort();
|
|
384
680
|
const prevState = readState(root);
|
|
681
|
+
const isResuming = prevState?.step === 3;
|
|
682
|
+
const now = new Date().toISOString();
|
|
385
683
|
writeState(root, {
|
|
386
684
|
feature,
|
|
387
685
|
step: 3,
|
|
388
686
|
label: STEP_LABELS[3],
|
|
389
|
-
startedAt:
|
|
390
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
687
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
688
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
391
689
|
});
|
|
392
690
|
logEvent(root, 'step', { step: 3, label: 'Confirm', feature });
|
|
393
691
|
stepHeader(3, 'Confirm', feature);
|
|
692
|
+
if (isResuming) {
|
|
693
|
+
printResumptionHeader(3);
|
|
694
|
+
}
|
|
394
695
|
console.log('Summarize what was built and get user confirmation.');
|
|
395
696
|
console.log();
|
|
396
697
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
397
|
-
checkbox(`Refresh the preview: \`
|
|
398
|
-
checkbox('Verify the page loads without errors (no error pages, no blank screens)');
|
|
399
|
-
checkbox('Verify all images and assets render correctly (no broken images)');
|
|
698
|
+
checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
|
|
400
699
|
checkbox('Verify API routes return valid data (curl each route)');
|
|
401
|
-
console.log(
|
|
700
|
+
console.log();
|
|
701
|
+
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
702
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
703
|
+
console.log(chalk.dim(' Include ALL page paths and ALL image URLs from seed data / API responses.'));
|
|
704
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images.'));
|
|
705
|
+
checkbox('Fix or remove any image that returns non-200 before continuing');
|
|
706
|
+
checkbox('Check for client-side errors: `codeyam editor client-errors`');
|
|
707
|
+
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
402
708
|
console.log();
|
|
403
709
|
console.log(chalk.bold('Then present to the user:'));
|
|
404
710
|
checkbox('Summarize what was built (routes, components, data)');
|
|
405
|
-
checkbox('
|
|
406
|
-
console.log();
|
|
407
|
-
console.log(chalk.
|
|
408
|
-
console.log(
|
|
409
|
-
|
|
410
|
-
|
|
711
|
+
checkbox('Navigate the preview to the feature\'s primary page: `codeyam editor preview \'{"path":"/your-page"}\'`');
|
|
712
|
+
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
713
|
+
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
714
|
+
console.log();
|
|
715
|
+
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
716
|
+
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
717
|
+
checkbox('Prompt the user to try key interactions: forms, navigation, buttons, etc.');
|
|
718
|
+
checkbox('Ask the user: "Does everything work as expected?"');
|
|
719
|
+
console.log();
|
|
720
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
721
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
|
|
722
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
411
723
|
chalk.dim(' — user describes changes, you make them, then re-present'));
|
|
412
724
|
console.log();
|
|
413
725
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
@@ -416,15 +728,20 @@ function printStep3(root, feature) {
|
|
|
416
728
|
// ─── Step 4: Deconstruct ──────────────────────────────────────────────
|
|
417
729
|
function printStep4(root, feature) {
|
|
418
730
|
const prevState = readState(root);
|
|
731
|
+
const isResuming = prevState?.step === 4;
|
|
732
|
+
const now = new Date().toISOString();
|
|
419
733
|
writeState(root, {
|
|
420
734
|
feature,
|
|
421
735
|
step: 4,
|
|
422
736
|
label: STEP_LABELS[4],
|
|
423
|
-
startedAt:
|
|
424
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
737
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
738
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
425
739
|
});
|
|
426
740
|
logEvent(root, 'step', { step: 4, label: 'Deconstruct', feature });
|
|
427
741
|
stepHeader(4, 'Deconstruct', feature);
|
|
742
|
+
if (isResuming) {
|
|
743
|
+
printResumptionHeader(4);
|
|
744
|
+
}
|
|
428
745
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
429
746
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
|
|
430
747
|
console.log();
|
|
@@ -460,16 +777,22 @@ function printStep4(root, feature) {
|
|
|
460
777
|
}
|
|
461
778
|
// ─── Step 5: Extract ──────────────────────────────────────────────────
|
|
462
779
|
function printStep5(root, feature) {
|
|
780
|
+
const port = getServerPort();
|
|
463
781
|
const prevState = readState(root);
|
|
782
|
+
const isResuming = prevState?.step === 5;
|
|
783
|
+
const now = new Date().toISOString();
|
|
464
784
|
writeState(root, {
|
|
465
785
|
feature,
|
|
466
786
|
step: 5,
|
|
467
787
|
label: STEP_LABELS[5],
|
|
468
|
-
startedAt:
|
|
469
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
788
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
789
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
470
790
|
});
|
|
471
791
|
logEvent(root, 'step', { step: 5, label: 'Extract', feature });
|
|
472
792
|
stepHeader(5, 'Extract', feature);
|
|
793
|
+
if (isResuming) {
|
|
794
|
+
printResumptionHeader(5);
|
|
795
|
+
}
|
|
473
796
|
console.log('Execute your extraction plan from step 4.');
|
|
474
797
|
console.log();
|
|
475
798
|
console.log(chalk.bold('Components:'));
|
|
@@ -495,6 +818,8 @@ function printStep5(root, feature) {
|
|
|
495
818
|
checkbox('Run all tests and verify they pass');
|
|
496
819
|
checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
|
|
497
820
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
821
|
+
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
|
|
822
|
+
console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
|
|
498
823
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
499
824
|
console.log();
|
|
500
825
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
@@ -503,15 +828,20 @@ function printStep5(root, feature) {
|
|
|
503
828
|
// ─── Step 6: Glossary ─────────────────────────────────────────────────
|
|
504
829
|
function printStep6(root, feature) {
|
|
505
830
|
const prevState = readState(root);
|
|
831
|
+
const isResuming = prevState?.step === 6;
|
|
832
|
+
const now = new Date().toISOString();
|
|
506
833
|
writeState(root, {
|
|
507
834
|
feature,
|
|
508
835
|
step: 6,
|
|
509
836
|
label: STEP_LABELS[6],
|
|
510
|
-
startedAt:
|
|
511
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
837
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
838
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
512
839
|
});
|
|
513
840
|
logEvent(root, 'step', { step: 6, label: 'Glossary', feature });
|
|
514
841
|
stepHeader(6, 'Glossary', feature);
|
|
842
|
+
if (isResuming) {
|
|
843
|
+
printResumptionHeader(6);
|
|
844
|
+
}
|
|
515
845
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
516
846
|
console.log();
|
|
517
847
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -535,20 +865,31 @@ function printStep6(root, feature) {
|
|
|
535
865
|
function printStep7(root, feature) {
|
|
536
866
|
const port = getServerPort();
|
|
537
867
|
const prevState = readState(root);
|
|
868
|
+
const isResuming = prevState?.step === 7;
|
|
869
|
+
const now = new Date().toISOString();
|
|
538
870
|
writeState(root, {
|
|
539
871
|
feature,
|
|
540
872
|
step: 7,
|
|
541
873
|
label: STEP_LABELS[7],
|
|
542
|
-
startedAt:
|
|
543
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
874
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
875
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
544
876
|
});
|
|
545
877
|
logEvent(root, 'step', { step: 7, label: 'Analyze', feature });
|
|
546
878
|
stepHeader(7, 'Analyze and Verify', feature);
|
|
879
|
+
if (isResuming) {
|
|
880
|
+
printResumptionHeader(7);
|
|
881
|
+
}
|
|
547
882
|
console.log('Verify visual components (via isolation routes) and library functions (via tests).');
|
|
548
883
|
console.log();
|
|
549
884
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
550
885
|
checkbox('List all files with new/modified visual components from step 5');
|
|
551
|
-
checkbox('
|
|
886
|
+
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
887
|
+
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
888
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
889
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
890
|
+
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
891
|
+
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
892
|
+
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
552
893
|
checkbox('For each visual component:');
|
|
553
894
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
554
895
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -559,8 +900,8 @@ function printStep7(root, feature) {
|
|
|
559
900
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
560
901
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
561
902
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
562
|
-
console.log(chalk.dim(' Remix: app/routes/
|
|
563
|
-
console.log(chalk.dim(' Next.js: app/
|
|
903
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
904
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
564
905
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
565
906
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
566
907
|
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
@@ -573,15 +914,17 @@ function printStep7(root, feature) {
|
|
|
573
914
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
574
915
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
575
916
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
576
|
-
console.log(chalk.dim(` "url":"/
|
|
917
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
577
918
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
919
|
+
console.log(chalk.dim(' Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size'));
|
|
920
|
+
console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
|
|
578
921
|
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
579
922
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
580
923
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
581
924
|
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
582
925
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
583
926
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
584
|
-
console.log(chalk.dim(' Isolation routes
|
|
927
|
+
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
585
928
|
console.log();
|
|
586
929
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
587
930
|
checkbox('Run ALL test files created in step 5');
|
|
@@ -590,29 +933,65 @@ function printStep7(root, feature) {
|
|
|
590
933
|
checkbox('If any test fails, fix the source code and re-run');
|
|
591
934
|
console.log();
|
|
592
935
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
936
|
+
console.log();
|
|
937
|
+
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
|
|
938
|
+
console.log();
|
|
939
|
+
console.log(chalk.bold('Build import graph (for change tracking):'));
|
|
940
|
+
checkbox('Run `codeyam editor analyze-imports`');
|
|
941
|
+
console.log(chalk.dim(' This populates the import graph so the system can detect impacted entities when code changes.'));
|
|
942
|
+
console.log(chalk.dim(' It must run AFTER the audit passes so the glossary and entity data are complete.'));
|
|
593
943
|
stopGate(7);
|
|
594
944
|
}
|
|
595
945
|
// ─── Step 8: App Scenarios ────────────────────────────────────────────
|
|
596
946
|
function printStep8(root, feature) {
|
|
597
947
|
const port = getServerPort();
|
|
598
948
|
const prevState = readState(root);
|
|
949
|
+
const isResuming = prevState?.step === 8;
|
|
950
|
+
const now = new Date().toISOString();
|
|
599
951
|
writeState(root, {
|
|
600
952
|
feature,
|
|
601
953
|
step: 8,
|
|
602
954
|
label: STEP_LABELS[8],
|
|
603
|
-
startedAt:
|
|
604
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
955
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
956
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
605
957
|
});
|
|
606
958
|
logEvent(root, 'step', { step: 8, label: 'App Scenarios', feature });
|
|
607
959
|
stepHeader(8, 'App Scenarios', feature);
|
|
960
|
+
if (isResuming) {
|
|
961
|
+
printResumptionHeader(8);
|
|
962
|
+
}
|
|
608
963
|
console.log('Create app-level scenarios representing different data states.');
|
|
609
964
|
console.log();
|
|
610
965
|
console.log(chalk.bold('Checklist:'));
|
|
611
|
-
checkbox('
|
|
612
|
-
console.log(chalk.dim('
|
|
613
|
-
console.log(chalk.dim('
|
|
614
|
-
checkbox('
|
|
615
|
-
console.log(chalk.dim(
|
|
966
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
967
|
+
console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
|
|
968
|
+
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
969
|
+
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
970
|
+
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
971
|
+
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
972
|
+
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
973
|
+
checkbox('Cover key data states for EVERY page/route (at least 2-3 scenarios per page)');
|
|
974
|
+
console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
|
|
975
|
+
console.log(chalk.dim(' Catalog: "Full Catalog", "Empty Catalog", "Single Category"'));
|
|
976
|
+
console.log(chalk.dim(' Detail: "Detail - With Reviews", "Detail - No Reviews", "Detail - High Rated"'));
|
|
977
|
+
console.log(chalk.dim(' Each scenario provides seed data to populate the database for that state'));
|
|
978
|
+
checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
|
|
979
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","seed":{"products":[...],"categories":[...]}}'`));
|
|
980
|
+
console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
|
|
981
|
+
checkbox('Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size');
|
|
982
|
+
console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
|
|
983
|
+
checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
|
|
984
|
+
console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
|
|
985
|
+
console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
|
|
986
|
+
console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
|
|
987
|
+
checkbox('For large seed data, write JSON to a project temp file and use @ prefix:');
|
|
988
|
+
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
989
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
990
|
+
checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
|
|
991
|
+
console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
|
|
992
|
+
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
993
|
+
checkbox('If no database, use component-style mock scenarios (unchanged):');
|
|
994
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
616
995
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
617
996
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
618
997
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
@@ -624,21 +1003,34 @@ function printStep8(root, feature) {
|
|
|
624
1003
|
function printStep9(root, feature) {
|
|
625
1004
|
const port = getServerPort();
|
|
626
1005
|
const prevState = readState(root);
|
|
1006
|
+
const isResuming = prevState?.step === 9;
|
|
1007
|
+
const now = new Date().toISOString();
|
|
627
1008
|
writeState(root, {
|
|
628
1009
|
feature,
|
|
629
1010
|
step: 9,
|
|
630
1011
|
label: STEP_LABELS[9],
|
|
631
|
-
startedAt:
|
|
632
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
1012
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1013
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
633
1014
|
});
|
|
634
1015
|
logEvent(root, 'step', { step: 9, label: 'User Scenarios', feature });
|
|
635
1016
|
stepHeader(9, 'User Scenarios', feature);
|
|
1017
|
+
if (isResuming) {
|
|
1018
|
+
printResumptionHeader(9);
|
|
1019
|
+
}
|
|
636
1020
|
console.log('Create per-persona scenarios if the app has users. Skip to step 10 if no users.');
|
|
637
1021
|
console.log();
|
|
638
1022
|
console.log(chalk.bold('If the app has users:'));
|
|
639
|
-
checkbox('
|
|
640
|
-
console.log(chalk.dim('
|
|
641
|
-
|
|
1023
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
1024
|
+
console.log(chalk.dim(' Reuse existing persona scenarios — update data to reflect current changes.'));
|
|
1025
|
+
console.log(chalk.dim(' Re-register with the same name to update. Only add new personas if needed.'));
|
|
1026
|
+
checkbox('Ensure scenarios for each user persona (admin, regular user, new user)');
|
|
1027
|
+
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1028
|
+
checkbox('If using seed-based scenarios, register with type "user" and baseScenario:');
|
|
1029
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"Admin User","type":"user","url":"/","baseScenario":"<app-scenario-id>","seed":{"users":[{"id":1,"role":"admin"}]}}'`));
|
|
1030
|
+
console.log(chalk.dim(' Always include "url" — the page to screenshot (e.g. "/", "/dashboard", "/drinks/1")'));
|
|
1031
|
+
console.log(chalk.dim(" The base scenario's seed data is merged with this scenario's seed data (overlay wins)."));
|
|
1032
|
+
checkbox('If the app uses auth or other patterns: see FEATURE_PATTERNS.md for per-persona scenario guidance');
|
|
1033
|
+
checkbox('If using mock-based scenarios, register with mockData as before:');
|
|
642
1034
|
console.log(chalk.dim(` codeyam editor register '{"name":"...","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
643
1035
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
644
1036
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
@@ -654,15 +1046,20 @@ function printStep9(root, feature) {
|
|
|
654
1046
|
function printStep10(root, feature) {
|
|
655
1047
|
const port = getServerPort();
|
|
656
1048
|
const prevState = readState(root);
|
|
1049
|
+
const isResuming = prevState?.step === 10;
|
|
1050
|
+
const now = new Date().toISOString();
|
|
657
1051
|
writeState(root, {
|
|
658
1052
|
feature,
|
|
659
1053
|
step: 10,
|
|
660
1054
|
label: STEP_LABELS[10],
|
|
661
|
-
startedAt:
|
|
662
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
1055
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1056
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
663
1057
|
});
|
|
664
1058
|
logEvent(root, 'step', { step: 10, label: 'Verify', feature });
|
|
665
1059
|
stepHeader(10, 'Verify', feature);
|
|
1060
|
+
if (isResuming) {
|
|
1061
|
+
printResumptionHeader(10);
|
|
1062
|
+
}
|
|
666
1063
|
console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
|
|
667
1064
|
console.log();
|
|
668
1065
|
console.log(chalk.bold('Component isolation screenshots — verify:'));
|
|
@@ -672,13 +1069,15 @@ function printStep10(root, feature) {
|
|
|
672
1069
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
|
|
673
1070
|
console.log();
|
|
674
1071
|
console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
|
|
675
|
-
checkbox(`Refresh the preview: \`
|
|
1072
|
+
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
676
1073
|
checkbox('Click through each app-level and user-persona scenario in the preview');
|
|
677
|
-
checkbox(
|
|
1074
|
+
checkbox('For seed-based scenarios: verify data renders correctly after switching');
|
|
1075
|
+
console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
|
|
1076
|
+
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
678
1077
|
console.log(chalk.yellow(' If `hasErrors` is true: list each scenario with errors, fix the source code,'));
|
|
679
1078
|
console.log(chalk.yellow(' re-register the affected scenarios, and re-check until hasErrors is false'));
|
|
680
1079
|
console.log(chalk.dim(' Common errors: React errors, failed fetches, undefined references, hydration mismatches'));
|
|
681
|
-
checkbox('Verify no broken images
|
|
1080
|
+
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
682
1081
|
console.log();
|
|
683
1082
|
console.log(chalk.bold('Library functions — test check:'));
|
|
684
1083
|
checkbox('Re-run all test files from step 5 to confirm they still pass');
|
|
@@ -688,99 +1087,131 @@ function printStep10(root, feature) {
|
|
|
688
1087
|
console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
|
|
689
1088
|
stopGate(10);
|
|
690
1089
|
}
|
|
691
|
-
// ─── Step 11:
|
|
1090
|
+
// ─── Step 11: Journal ─────────────────────────────────────────────────
|
|
692
1091
|
function printStep11(root, feature) {
|
|
693
1092
|
const port = getServerPort();
|
|
694
1093
|
const prevState = readState(root);
|
|
1094
|
+
const isResuming = prevState?.step === 11;
|
|
1095
|
+
const now = new Date().toISOString();
|
|
695
1096
|
writeState(root, {
|
|
696
1097
|
feature,
|
|
697
1098
|
step: 11,
|
|
698
1099
|
label: STEP_LABELS[11],
|
|
699
|
-
startedAt:
|
|
700
|
-
featureStartedAt: prevState?.featureStartedAt ||
|
|
1100
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1101
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
701
1102
|
});
|
|
702
|
-
logEvent(root, 'step', { step: 11, label: '
|
|
703
|
-
stepHeader(11, '
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
checkbox('List all components and functions created');
|
|
709
|
-
checkbox('List glossary entries added in step 6');
|
|
710
|
-
checkbox('List all scenarios (app-level and user-persona)');
|
|
711
|
-
checkbox('Report analysis status (completed/in-progress from step 7)');
|
|
712
|
-
checkbox('Report verification status from step 10 (all clean?)');
|
|
713
|
-
checkbox(`Refresh the preview: \`curl -s -X POST http://localhost:${port}/api/dev-mode-refresh\``);
|
|
714
|
-
checkbox(`Get scenario list: \`curl -s http://localhost:${port}/api/editor-scenarios\``);
|
|
715
|
-
checkbox('Present scenarios in a markdown table — one scenario per row');
|
|
716
|
-
console.log(chalk.yellow(' EVERY scenario must use {{scenario:Name:ID}} — the terminal makes these clickable.'));
|
|
717
|
-
console.log(chalk.yellow(' Strip the component prefix: "DrinkCard - Default" → just "Default".'));
|
|
718
|
-
console.log(chalk.yellow(' Component name on first row of each group, blank for subsequent rows.'));
|
|
719
|
-
console.log();
|
|
720
|
-
console.log(chalk.dim(' Example:'));
|
|
721
|
-
console.log(chalk.dim(' | Component | Scenario |'));
|
|
722
|
-
console.log(chalk.dim(' |---|---|'));
|
|
723
|
-
console.log(chalk.dim(' | **App** | {{scenario:Full Catalog:abc}} |'));
|
|
724
|
-
console.log(chalk.dim(' | | {{scenario:Empty:def}} |'));
|
|
725
|
-
console.log(chalk.dim(' | | {{scenario:Error:ghi}} |'));
|
|
726
|
-
console.log(chalk.dim(' | **StarRating** | {{scenario:Perfect:jkl}} |'));
|
|
727
|
-
console.log(chalk.dim(' | | {{scenario:Medium:mno}} |'));
|
|
728
|
-
console.log();
|
|
729
|
-
console.log(chalk.bold('Phase 2 — Screenshot & error verification:'));
|
|
730
|
-
console.log(chalk.yellow(' CRITICAL: Verify screenshots AND check for client-side errors.'));
|
|
731
|
-
console.log();
|
|
732
|
-
console.log(chalk.bold(' Component isolation screenshots:'));
|
|
733
|
-
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
734
|
-
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
735
|
-
console.log();
|
|
736
|
-
console.log(chalk.bold(' Client-side errors:'));
|
|
737
|
-
checkbox(`Check for errors: \`curl -s http://localhost:${port}/api/editor-client-errors\``);
|
|
738
|
-
checkbox('If `hasErrors` is true, list the errors and fix them in the source code');
|
|
739
|
-
checkbox('After fixing, re-capture affected scenarios to clear the errors');
|
|
740
|
-
console.log();
|
|
741
|
-
checkbox('Do not proceed until all components have screenshots and client errors are resolved');
|
|
742
|
-
console.log();
|
|
743
|
-
checkbox('Switch the active scenario to the best demo state for the feature');
|
|
744
|
-
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-switch-scenario \\`));
|
|
745
|
-
console.log(chalk.dim(` -H 'Content-Type: application/json' -d '{"scenarioSlug":"...","scenarioId":"..."}'`));
|
|
746
|
-
console.log(chalk.dim(' Pick the scenario that best showcases the feature (not an error/empty state)'));
|
|
747
|
-
checkbox('Present the summary (with screenshot and error status) to the user');
|
|
1103
|
+
logEvent(root, 'step', { step: 11, label: 'Journal', feature });
|
|
1104
|
+
stepHeader(11, 'Journal', feature);
|
|
1105
|
+
if (isResuming) {
|
|
1106
|
+
printResumptionHeader(11);
|
|
1107
|
+
}
|
|
1108
|
+
console.log('Create or update the journal entry for this feature.');
|
|
748
1109
|
console.log();
|
|
749
|
-
console.log(chalk.bold('
|
|
1110
|
+
console.log(chalk.bold('Checklist:'));
|
|
750
1111
|
checkbox('Write a concise description of what was built (2-3 sentences)');
|
|
751
1112
|
checkbox(`First time at step 11 — create journal entry with ALL session screenshots:`);
|
|
752
|
-
console.log(chalk.dim(`
|
|
753
|
-
console.log(chalk.dim(` -H 'Content-Type: application/json' \\`));
|
|
754
|
-
console.log(chalk.dim(` -d '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
1113
|
+
console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
755
1114
|
console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
|
|
756
|
-
checkbox(`Returning
|
|
757
|
-
console.log(chalk.dim(`
|
|
758
|
-
console.log(chalk.dim(
|
|
759
|
-
console.log(
|
|
1115
|
+
checkbox(`Returning to step 11 (before commit) — update the existing journal entry:`);
|
|
1116
|
+
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1117
|
+
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1118
|
+
console.log();
|
|
1119
|
+
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
|
|
1120
|
+
stopGate(11);
|
|
1121
|
+
}
|
|
1122
|
+
// ─── Step 12: Review ──────────────────────────────────────────────────
|
|
1123
|
+
function printStep12(root, feature) {
|
|
1124
|
+
const port = getServerPort();
|
|
1125
|
+
const prevState = readState(root);
|
|
1126
|
+
const isResuming = prevState?.step === 12;
|
|
1127
|
+
const now = new Date().toISOString();
|
|
1128
|
+
writeState(root, {
|
|
1129
|
+
feature,
|
|
1130
|
+
step: 12,
|
|
1131
|
+
label: STEP_LABELS[12],
|
|
1132
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1133
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1134
|
+
});
|
|
1135
|
+
logEvent(root, 'step', { step: 12, label: 'Review', feature });
|
|
1136
|
+
stepHeader(12, 'Review', feature);
|
|
1137
|
+
if (isResuming) {
|
|
1138
|
+
printResumptionHeader(12);
|
|
1139
|
+
}
|
|
1140
|
+
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1141
|
+
console.log();
|
|
1142
|
+
console.log(chalk.bold('Checklist (do all of this silently):'));
|
|
1143
|
+
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1144
|
+
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
1145
|
+
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
1146
|
+
checkbox(`Check for client errors: \`codeyam editor client-errors\``);
|
|
1147
|
+
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1148
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1149
|
+
checkbox('Fix or remove any broken images before continuing');
|
|
1150
|
+
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1151
|
+
checkbox('Do not proceed until all checks pass');
|
|
1152
|
+
stopGate(12);
|
|
1153
|
+
}
|
|
1154
|
+
// ─── Step 13: Present ─────────────────────────────────────────────────
|
|
1155
|
+
function printStep13(root, feature) {
|
|
1156
|
+
const port = getServerPort();
|
|
1157
|
+
const prevState = readState(root);
|
|
1158
|
+
const isResuming = prevState?.step === 13;
|
|
1159
|
+
const now = new Date().toISOString();
|
|
1160
|
+
writeState(root, {
|
|
1161
|
+
feature,
|
|
1162
|
+
step: 13,
|
|
1163
|
+
label: STEP_LABELS[13],
|
|
1164
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1165
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1166
|
+
});
|
|
1167
|
+
logEvent(root, 'step', { step: 13, label: 'Present', feature });
|
|
1168
|
+
stepHeader(13, 'Present', feature);
|
|
1169
|
+
if (isResuming) {
|
|
1170
|
+
printResumptionHeader(13);
|
|
1171
|
+
}
|
|
1172
|
+
console.log('Present the results to the user and get their approval.');
|
|
760
1173
|
console.log();
|
|
761
|
-
console.log(chalk.bold('
|
|
762
|
-
|
|
1174
|
+
console.log(chalk.bold('Checklist:'));
|
|
1175
|
+
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1176
|
+
console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
|
|
1177
|
+
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1178
|
+
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1179
|
+
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1180
|
+
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1181
|
+
checkbox('Switch the preview to that scenario using its `id`:');
|
|
1182
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1183
|
+
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1184
|
+
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1185
|
+
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1186
|
+
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1187
|
+
checkbox('Report test count and audit status (one line)');
|
|
1188
|
+
console.log();
|
|
1189
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1190
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
763
1191
|
chalk.dim(' — git commit all changes and record in journal'));
|
|
764
|
-
console.log(chalk.yellow(' "I\'d like to make some changes"') +
|
|
1192
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
765
1193
|
chalk.dim(' — describe changes, then re-verify'));
|
|
766
1194
|
console.log();
|
|
767
1195
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
768
|
-
checkbox(`
|
|
1196
|
+
checkbox(`Hide the results panel first: \`codeyam editor hide-results\``);
|
|
1197
|
+
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
769
1198
|
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
770
|
-
checkbox(`Update journal with commit SHA: \`
|
|
1199
|
+
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
771
1200
|
console.log(chalk.green(' Then run: ') +
|
|
772
|
-
chalk.bold('codeyam editor
|
|
1201
|
+
chalk.bold('codeyam editor steps') +
|
|
773
1202
|
chalk.green(' to start the next feature'));
|
|
774
1203
|
console.log();
|
|
775
|
-
console.log(chalk.bold('If the user
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
console.log(
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1204
|
+
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
1205
|
+
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
1206
|
+
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
1207
|
+
console.log();
|
|
1208
|
+
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1209
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
1210
|
+
checkbox('Ask what changes the user wants (if not already clear)');
|
|
1211
|
+
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1212
|
+
checkbox('THEN make the requested changes and follow the checklist');
|
|
1213
|
+
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1214
|
+
stopGate(13, { confirm: true });
|
|
784
1215
|
}
|
|
785
1216
|
// ─── Command definition ───────────────────────────────────────────────
|
|
786
1217
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
@@ -837,8 +1268,9 @@ async function handleAnalyzeImports() {
|
|
|
837
1268
|
await initializeEnvironment();
|
|
838
1269
|
const entities = await loadEntities({});
|
|
839
1270
|
if (!entities || entities.length === 0) {
|
|
840
|
-
progress.
|
|
841
|
-
|
|
1271
|
+
progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
|
|
1272
|
+
console.log(JSON.stringify({ imports: {}, entities: {} }));
|
|
1273
|
+
return;
|
|
842
1274
|
}
|
|
843
1275
|
// Deduplicate to latest versions
|
|
844
1276
|
const latestByKey = new Map();
|
|
@@ -894,7 +1326,161 @@ async function handleAnalyzeImports() {
|
|
|
894
1326
|
const output = { imports, entities: entityData };
|
|
895
1327
|
console.log(JSON.stringify(output, null, 2));
|
|
896
1328
|
}
|
|
1329
|
+
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
1330
|
+
/**
|
|
1331
|
+
* Validate seed data structure and check for a seed adapter.
|
|
1332
|
+
* Usage: codeyam editor validate-seed '{"products":[...]}'
|
|
1333
|
+
*/
|
|
1334
|
+
function handleValidateSeed(jsonArg) {
|
|
1335
|
+
const root = process.cwd();
|
|
1336
|
+
// Check for seed adapter
|
|
1337
|
+
const adapterPath = detectSeedAdapter(root);
|
|
1338
|
+
if (adapterPath) {
|
|
1339
|
+
console.log(chalk.green(`Seed adapter found: ${adapterPath}`));
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
console.log(chalk.yellow('No seed adapter found. Create .codeyam/seed-adapter.ts to use seed-based scenarios.'));
|
|
1343
|
+
}
|
|
1344
|
+
if (!jsonArg) {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
let data;
|
|
1348
|
+
try {
|
|
1349
|
+
data = JSON.parse(jsonArg);
|
|
1350
|
+
}
|
|
1351
|
+
catch {
|
|
1352
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
1353
|
+
process.exit(1);
|
|
1354
|
+
}
|
|
1355
|
+
const result = validateSeedData(data);
|
|
1356
|
+
if (result.valid) {
|
|
1357
|
+
const tables = Object.keys(data);
|
|
1358
|
+
const totalRows = tables.reduce((sum, t) => sum + (Array.isArray(data[t]) ? data[t].length : 0), 0);
|
|
1359
|
+
console.log(chalk.green(`Seed data valid: ${tables.length} table(s), ${totalRows} total row(s)`));
|
|
1360
|
+
for (const table of tables) {
|
|
1361
|
+
const rows = Array.isArray(data[table])
|
|
1362
|
+
? data[table].length
|
|
1363
|
+
: 0;
|
|
1364
|
+
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
else {
|
|
1368
|
+
console.error(chalk.red('Seed data validation failed:'));
|
|
1369
|
+
for (const err of result.errors) {
|
|
1370
|
+
console.error(chalk.red(` - ${err}`));
|
|
1371
|
+
}
|
|
1372
|
+
process.exit(1);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
1376
|
+
/**
|
|
1377
|
+
* `codeyam editor isolate StarRating CategoryBadge DrinkCard`
|
|
1378
|
+
*
|
|
1379
|
+
* Creates isolation route directories and the layout guard file.
|
|
1380
|
+
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
1381
|
+
*/
|
|
1382
|
+
function handleIsolate(componentNames) {
|
|
1383
|
+
const root = process.cwd();
|
|
1384
|
+
if (componentNames.length === 0) {
|
|
1385
|
+
console.error(chalk.red('Usage: codeyam editor isolate ComponentA ComponentB ...'));
|
|
1386
|
+
process.exit(1);
|
|
1387
|
+
}
|
|
1388
|
+
const isolateDir = path.join(root, 'app', 'codeyam-isolate');
|
|
1389
|
+
// Create layout.tsx with production guard if missing
|
|
1390
|
+
const layoutPath = path.join(isolateDir, 'layout.tsx');
|
|
1391
|
+
if (!fs.existsSync(layoutPath)) {
|
|
1392
|
+
fs.mkdirSync(isolateDir, { recursive: true });
|
|
1393
|
+
fs.writeFileSync(layoutPath, [
|
|
1394
|
+
'import { notFound } from "next/navigation";',
|
|
1395
|
+
'',
|
|
1396
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
1397
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
1398
|
+
' return <>{children}</>;',
|
|
1399
|
+
'}',
|
|
1400
|
+
'',
|
|
1401
|
+
].join('\n'), 'utf8');
|
|
1402
|
+
console.log(chalk.green(`Created layout guard: app/codeyam-isolate/layout.tsx`));
|
|
1403
|
+
}
|
|
1404
|
+
// Create a directory for each component
|
|
1405
|
+
const created = [];
|
|
1406
|
+
const existed = [];
|
|
1407
|
+
for (const name of componentNames) {
|
|
1408
|
+
const dir = path.join(isolateDir, name);
|
|
1409
|
+
if (fs.existsSync(dir)) {
|
|
1410
|
+
existed.push(name);
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1414
|
+
created.push(name);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (created.length > 0) {
|
|
1418
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
1419
|
+
}
|
|
1420
|
+
if (existed.length > 0) {
|
|
1421
|
+
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
897
1424
|
// ─── Register subcommand ──────────────────────────────────────────────
|
|
1425
|
+
/**
|
|
1426
|
+
* Format API subcommand results as concise one-line summaries.
|
|
1427
|
+
* Prevents Claude from piping output to python3 just to extract key fields.
|
|
1428
|
+
* Returns null for subcommands that should keep raw JSON output.
|
|
1429
|
+
*/
|
|
1430
|
+
function formatApiSubcommandResult(subcommand, data) {
|
|
1431
|
+
if (!data || typeof data !== 'object')
|
|
1432
|
+
return null;
|
|
1433
|
+
switch (subcommand) {
|
|
1434
|
+
case 'journal': {
|
|
1435
|
+
const parts = [`success=${data.success}`];
|
|
1436
|
+
if (data.entry?.time)
|
|
1437
|
+
parts.push(`time="${data.entry.time}"`);
|
|
1438
|
+
if (data.entry?.title)
|
|
1439
|
+
parts.push(`title="${data.entry.title}"`);
|
|
1440
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
1441
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
1442
|
+
}
|
|
1443
|
+
return parts.join(' ');
|
|
1444
|
+
}
|
|
1445
|
+
case 'journal-update': {
|
|
1446
|
+
const parts = [`success=${data.success}`];
|
|
1447
|
+
if (data.entry?.time)
|
|
1448
|
+
parts.push(`time="${data.entry.time}"`);
|
|
1449
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
1450
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
1451
|
+
}
|
|
1452
|
+
return parts.join(' ');
|
|
1453
|
+
}
|
|
1454
|
+
case 'commit': {
|
|
1455
|
+
const parts = [`success=${data.success}`];
|
|
1456
|
+
if (data.sha)
|
|
1457
|
+
parts.push(`sha=${data.sha}`);
|
|
1458
|
+
if (data.message)
|
|
1459
|
+
parts.push(`message="${data.message.split('\n')[0]}"`);
|
|
1460
|
+
return parts.join(' ');
|
|
1461
|
+
}
|
|
1462
|
+
case 'preview': {
|
|
1463
|
+
const parts = [
|
|
1464
|
+
`success=${!!data.ok || data.success !== false}`,
|
|
1465
|
+
];
|
|
1466
|
+
if (data.path)
|
|
1467
|
+
parts.push(`path="${data.path}"`);
|
|
1468
|
+
if (data.scenarioId)
|
|
1469
|
+
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
1470
|
+
if (data.sessionsNotified != null)
|
|
1471
|
+
parts.push(`notified=${data.sessionsNotified}`);
|
|
1472
|
+
return parts.join(' ');
|
|
1473
|
+
}
|
|
1474
|
+
case 'dev-server': {
|
|
1475
|
+
if (data.status) {
|
|
1476
|
+
return `status=${data.status} pid=${data.pid || 'none'} port=${data.port || 'none'}`;
|
|
1477
|
+
}
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
default:
|
|
1481
|
+
return null; // journal-list, client-errors, show/hide-results: keep full JSON
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
898
1484
|
/**
|
|
899
1485
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
900
1486
|
*
|
|
@@ -904,18 +1490,19 @@ async function handleAnalyzeImports() {
|
|
|
904
1490
|
async function handleRegister(jsonArg) {
|
|
905
1491
|
if (!jsonArg) {
|
|
906
1492
|
console.error(chalk.red('Error: JSON argument required.'));
|
|
907
|
-
console.error(chalk.dim(' Usage: codeyam editor register \'{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/
|
|
1493
|
+
console.error(chalk.dim(' Usage: codeyam editor register \'{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default"}\''));
|
|
1494
|
+
console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
|
|
908
1495
|
process.exit(1);
|
|
909
1496
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
1497
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
1498
|
+
if (parsed.error) {
|
|
1499
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
1500
|
+
if (parsed.error === 'Invalid JSON') {
|
|
1501
|
+
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
1502
|
+
}
|
|
917
1503
|
process.exit(1);
|
|
918
1504
|
}
|
|
1505
|
+
const body = parsed.body;
|
|
919
1506
|
const port = getServerPort();
|
|
920
1507
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
921
1508
|
try {
|
|
@@ -925,8 +1512,34 @@ async function handleRegister(jsonArg) {
|
|
|
925
1512
|
body: JSON.stringify(body),
|
|
926
1513
|
});
|
|
927
1514
|
const data = await res.json();
|
|
928
|
-
|
|
1515
|
+
// Print concise summary instead of raw JSON so Claude doesn't need python3
|
|
1516
|
+
const parts = [];
|
|
1517
|
+
parts.push(`success=${data.success}`);
|
|
1518
|
+
if (data.scenario?.name)
|
|
1519
|
+
parts.push(`name="${data.scenario.name}"`);
|
|
1520
|
+
parts.push(`screenshot=${!!data.screenshotCaptured}`);
|
|
1521
|
+
if (data.clientErrors?.length > 0) {
|
|
1522
|
+
parts.push(chalk.red(`errors=${data.clientErrors.length}`));
|
|
1523
|
+
}
|
|
1524
|
+
else {
|
|
1525
|
+
parts.push(`errors=0`);
|
|
1526
|
+
}
|
|
1527
|
+
if (data.seedResult)
|
|
1528
|
+
parts.push(`seed=${data.seedResult.success}`);
|
|
1529
|
+
if (data.captureError)
|
|
1530
|
+
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
1531
|
+
console.log(parts.join(' '));
|
|
1532
|
+
// Surface client errors prominently so they can't be missed
|
|
1533
|
+
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
1534
|
+
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
1535
|
+
for (const err of data.clientErrors) {
|
|
1536
|
+
console.log(chalk.red(` → ${err}`));
|
|
1537
|
+
}
|
|
1538
|
+
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
1539
|
+
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
1540
|
+
}
|
|
929
1541
|
if (!res.ok) {
|
|
1542
|
+
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
930
1543
|
process.exit(1);
|
|
931
1544
|
}
|
|
932
1545
|
}
|
|
@@ -938,15 +1551,775 @@ async function handleRegister(jsonArg) {
|
|
|
938
1551
|
process.exit(1);
|
|
939
1552
|
}
|
|
940
1553
|
}
|
|
1554
|
+
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
1555
|
+
/**
|
|
1556
|
+
* `codeyam editor dependents <EntityName>`
|
|
1557
|
+
*
|
|
1558
|
+
* Walks the importedBy reverse dependency tree to find all ancestors of a
|
|
1559
|
+
* given entity — i.e., every component or page that transitively imports it.
|
|
1560
|
+
* Used after code changes to determine what needs recapturing.
|
|
1561
|
+
*/
|
|
1562
|
+
async function handleDependents(entityName) {
|
|
1563
|
+
if (!entityName) {
|
|
1564
|
+
console.error(chalk.red('Error: Entity name required.'));
|
|
1565
|
+
console.error(chalk.dim(' Usage: codeyam editor dependents DrinkCard'));
|
|
1566
|
+
process.exit(1);
|
|
1567
|
+
}
|
|
1568
|
+
const progress = new ProgressReporter();
|
|
1569
|
+
progress.start('Loading entities from database...');
|
|
1570
|
+
await initializeEnvironment();
|
|
1571
|
+
const allEntities = await loadEntities({});
|
|
1572
|
+
if (!allEntities || allEntities.length === 0) {
|
|
1573
|
+
progress.fail('No entities found in database');
|
|
1574
|
+
console.error(chalk.dim(' Run codeyam editor analyze-imports first to populate entity data.'));
|
|
1575
|
+
process.exit(1);
|
|
1576
|
+
}
|
|
1577
|
+
// Find the target entity by name (case-insensitive match)
|
|
1578
|
+
const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
1579
|
+
if (targetEntities.length === 0) {
|
|
1580
|
+
progress.fail(`Entity "${entityName}" not found in database`);
|
|
1581
|
+
const names = [...new Set(allEntities.map((e) => e.name))].sort();
|
|
1582
|
+
console.error(chalk.dim(` Available entities: ${names.join(', ')}`));
|
|
1583
|
+
process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
progress.succeed('Entities loaded');
|
|
1586
|
+
// Build lookup: entityName -> Set<entityName> of entities that import it.
|
|
1587
|
+
// Each entity's metadata.importedBy is the pre-computed reverse graph:
|
|
1588
|
+
// { [filePath]: { [importerName]: { shas: string[] } } }
|
|
1589
|
+
const importedByName = new Map();
|
|
1590
|
+
for (const entity of allEntities) {
|
|
1591
|
+
const importedBy = entity.metadata?.importedBy;
|
|
1592
|
+
if (!importedBy || typeof importedBy !== 'object')
|
|
1593
|
+
continue;
|
|
1594
|
+
const importers = new Set();
|
|
1595
|
+
for (const filePath of Object.keys(importedBy)) {
|
|
1596
|
+
for (const importerName of Object.keys(importedBy[filePath])) {
|
|
1597
|
+
importers.add(importerName);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (importers.size > 0) {
|
|
1601
|
+
importedByName.set(entity.name, importers);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
// BFS walk up the dependency tree from the changed entity
|
|
1605
|
+
const visited = new Set();
|
|
1606
|
+
const result = [];
|
|
1607
|
+
const queue = [
|
|
1608
|
+
{ name: entityName, depth: 0 },
|
|
1609
|
+
];
|
|
1610
|
+
visited.add(entityName.toLowerCase());
|
|
1611
|
+
// Collect entity types for display
|
|
1612
|
+
const entityTypes = new Map();
|
|
1613
|
+
for (const e of allEntities) {
|
|
1614
|
+
if (!entityTypes.has(e.name)) {
|
|
1615
|
+
entityTypes.set(e.name, e.entityType || 'unknown');
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
while (queue.length > 0) {
|
|
1619
|
+
const { name, depth } = queue.shift();
|
|
1620
|
+
result.push({
|
|
1621
|
+
name,
|
|
1622
|
+
entityType: entityTypes.get(name) || 'unknown',
|
|
1623
|
+
depth,
|
|
1624
|
+
});
|
|
1625
|
+
// Find all entities that import this one
|
|
1626
|
+
const importers = importedByName.get(name);
|
|
1627
|
+
if (!importers)
|
|
1628
|
+
continue;
|
|
1629
|
+
for (const importerName of importers) {
|
|
1630
|
+
if (visited.has(importerName.toLowerCase()))
|
|
1631
|
+
continue;
|
|
1632
|
+
visited.add(importerName.toLowerCase());
|
|
1633
|
+
queue.push({ name: importerName, depth: depth + 1 });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
// Output
|
|
1637
|
+
if (result.length <= 1) {
|
|
1638
|
+
console.log(chalk.yellow(`No dependents found for "${entityName}".`));
|
|
1639
|
+
console.log(chalk.dim(' This entity is not imported by any other known entities.'));
|
|
1640
|
+
console.log(chalk.dim(' Run codeyam editor analyze-imports to populate import data.'));
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
console.log(chalk.bold(`Dependency tree for ${entityName} (${result.length} entities):`));
|
|
1644
|
+
console.log();
|
|
1645
|
+
for (const entry of result) {
|
|
1646
|
+
const indent = ' '.repeat(entry.depth);
|
|
1647
|
+
const prefix = entry.depth === 0 ? chalk.cyan('●') : chalk.dim('├──');
|
|
1648
|
+
const label = entry.depth === 0
|
|
1649
|
+
? chalk.cyan(`${entry.name} (changed)`)
|
|
1650
|
+
: chalk.white(entry.name);
|
|
1651
|
+
const type = chalk.dim(`[${entry.entityType}]`);
|
|
1652
|
+
console.log(`${indent}${prefix} ${label} ${type}`);
|
|
1653
|
+
}
|
|
1654
|
+
console.log();
|
|
1655
|
+
console.log(chalk.dim('All listed entities need their scenarios recaptured after changes.'));
|
|
1656
|
+
}
|
|
1657
|
+
// ─── Change subcommand ───────────────────────────────────────────────
|
|
1658
|
+
/**
|
|
1659
|
+
* `codeyam editor change <feature>`
|
|
1660
|
+
*
|
|
1661
|
+
* Prints a condensed post-change checklist that guides Claude through
|
|
1662
|
+
* re-verifying after user-requested modifications. This is the "change
|
|
1663
|
+
* loop" — it replaces the freeform "make changes" path with structured
|
|
1664
|
+
* instructions that always loop back to step 13 present.
|
|
1665
|
+
*/
|
|
1666
|
+
function handleChange(feature) {
|
|
1667
|
+
const root = getProjectRoot();
|
|
1668
|
+
if (!feature) {
|
|
1669
|
+
// Try to read feature from state
|
|
1670
|
+
const state = readState(root);
|
|
1671
|
+
if (state?.feature) {
|
|
1672
|
+
feature = state.feature;
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
console.error(chalk.red('Error: Feature name required.'));
|
|
1676
|
+
console.error(chalk.dim(' Usage: codeyam editor change "My Feature"'));
|
|
1677
|
+
process.exit(1);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
const port = getServerPort();
|
|
1681
|
+
console.log();
|
|
1682
|
+
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
1683
|
+
console.log(chalk.dim(`Feature: ${feature}`));
|
|
1684
|
+
console.log();
|
|
1685
|
+
console.log('The user has requested changes. Follow this checklist after making them.');
|
|
1686
|
+
console.log();
|
|
1687
|
+
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
1688
|
+
console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
|
|
1689
|
+
console.log(chalk.cyan(` codeyam editor preview`));
|
|
1690
|
+
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
1691
|
+
console.log();
|
|
1692
|
+
console.log(chalk.bold('0. Close the results panel:'));
|
|
1693
|
+
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
1694
|
+
console.log();
|
|
1695
|
+
console.log(chalk.bold('1. Re-register affected component scenarios:'));
|
|
1696
|
+
checkbox('For each component you modified, re-register ALL its scenarios');
|
|
1697
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName"}'`));
|
|
1698
|
+
checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
|
|
1699
|
+
console.log();
|
|
1700
|
+
console.log(chalk.bold('2. Re-run affected tests:'));
|
|
1701
|
+
checkbox('Re-run test files for any functions you modified');
|
|
1702
|
+
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
1703
|
+
checkbox('If any test fails, fix the source code and re-run');
|
|
1704
|
+
checkbox('If you re-seeded the database, restart the dev server to pick up fresh data');
|
|
1705
|
+
console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
|
|
1706
|
+
console.log();
|
|
1707
|
+
console.log(chalk.bold('3. Re-capture app-level scenarios:'));
|
|
1708
|
+
checkbox('Run `codeyam editor analyze-imports` to refresh the import graph after code changes');
|
|
1709
|
+
checkbox('If component changes affect app-level pages, re-register affected app scenarios');
|
|
1710
|
+
checkbox(`Use \`codeyam editor dependents ComponentName\` to find what needs recapturing`);
|
|
1711
|
+
console.log();
|
|
1712
|
+
console.log(chalk.bold('4. Verify completeness:'));
|
|
1713
|
+
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1714
|
+
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
|
|
1715
|
+
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
1716
|
+
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
1717
|
+
checkbox('Fix any errors, then re-register affected scenarios');
|
|
1718
|
+
console.log();
|
|
1719
|
+
console.log(chalk.bold('5. Update the existing journal entry:'));
|
|
1720
|
+
checkbox('Get existing entry: `codeyam editor journal-list`');
|
|
1721
|
+
checkbox('Find the uncommitted entry (commitSha is null) from this feature session');
|
|
1722
|
+
checkbox(`Update it: \`codeyam editor journal-update '{"time":"<entry time>","description":"<updated description including changes>","includeSessionScenarios":true}'\``);
|
|
1723
|
+
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
1724
|
+
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
1725
|
+
console.log();
|
|
1726
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1727
|
+
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
1728
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
1729
|
+
chalk.bold(`codeyam editor 13`));
|
|
1730
|
+
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
1731
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
1732
|
+
console.log();
|
|
1733
|
+
}
|
|
1734
|
+
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
1735
|
+
/**
|
|
1736
|
+
* `codeyam editor audit`
|
|
1737
|
+
*
|
|
1738
|
+
* Fetches the /api/editor-audit endpoint and prints a checklist showing
|
|
1739
|
+
* which glossary components have registered scenarios and which functions
|
|
1740
|
+
* have test files. Exits with code 1 if anything is missing.
|
|
1741
|
+
*/
|
|
1742
|
+
async function handleAudit() {
|
|
1743
|
+
const port = getServerPort();
|
|
1744
|
+
const url = `http://localhost:${port}/api/editor-audit`;
|
|
1745
|
+
let data;
|
|
1746
|
+
try {
|
|
1747
|
+
const res = await fetch(url);
|
|
1748
|
+
if (!res.ok) {
|
|
1749
|
+
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
1750
|
+
process.exit(1);
|
|
1751
|
+
}
|
|
1752
|
+
data = await res.json();
|
|
1753
|
+
}
|
|
1754
|
+
catch (err) {
|
|
1755
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
1756
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
1757
|
+
process.exit(1);
|
|
1758
|
+
}
|
|
1759
|
+
const { components, functions, summary } = data;
|
|
1760
|
+
console.log();
|
|
1761
|
+
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
1762
|
+
console.log();
|
|
1763
|
+
// Components
|
|
1764
|
+
if (components.length > 0) {
|
|
1765
|
+
console.log(chalk.bold('Components (scenarios):'));
|
|
1766
|
+
for (const c of components) {
|
|
1767
|
+
const icon = c.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
|
|
1768
|
+
let detail;
|
|
1769
|
+
if (c.status === 'has_errors') {
|
|
1770
|
+
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
1771
|
+
}
|
|
1772
|
+
else if (c.status === 'ok') {
|
|
1773
|
+
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
detail = chalk.red(' — no scenarios registered');
|
|
1777
|
+
}
|
|
1778
|
+
console.log(` ${icon} ${c.name}${detail}`);
|
|
1779
|
+
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
1780
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
1781
|
+
console.log(chalk.red(` → ${err}`));
|
|
1782
|
+
}
|
|
1783
|
+
if (c.clientErrors.length > 3) {
|
|
1784
|
+
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
console.log();
|
|
1789
|
+
}
|
|
1790
|
+
// Functions
|
|
1791
|
+
if (functions.length > 0) {
|
|
1792
|
+
console.log(chalk.bold('Functions (test files):'));
|
|
1793
|
+
for (const f of functions) {
|
|
1794
|
+
const icon = f.status === 'ok' ? chalk.green('✓') : chalk.red('✗');
|
|
1795
|
+
let detail;
|
|
1796
|
+
switch (f.status) {
|
|
1797
|
+
case 'ok':
|
|
1798
|
+
detail = chalk.dim(` (${f.testFile})`);
|
|
1799
|
+
break;
|
|
1800
|
+
case 'failing':
|
|
1801
|
+
detail = chalk.red(` — tests failing: ${f.testFile}`);
|
|
1802
|
+
break;
|
|
1803
|
+
case 'name_mismatch':
|
|
1804
|
+
detail = chalk.red(` — tests pass but missing top-level describe("${f.name}", ...) — tests won't display in UI`);
|
|
1805
|
+
break;
|
|
1806
|
+
case 'missing':
|
|
1807
|
+
default:
|
|
1808
|
+
detail = f.testFile
|
|
1809
|
+
? chalk.red(` — test file missing: ${f.testFile}`)
|
|
1810
|
+
: chalk.red(' — no test file specified');
|
|
1811
|
+
break;
|
|
1812
|
+
}
|
|
1813
|
+
console.log(` ${icon} ${f.name}${detail}`);
|
|
1814
|
+
}
|
|
1815
|
+
console.log();
|
|
1816
|
+
}
|
|
1817
|
+
// Summary
|
|
1818
|
+
const allOk = summary.allPassing;
|
|
1819
|
+
if (allOk) {
|
|
1820
|
+
console.log(chalk.green.bold('All checks passed!') +
|
|
1821
|
+
chalk.dim(` (${summary.totalComponents} component${summary.totalComponents !== 1 ? 's' : ''}, ${summary.totalFunctions} function${summary.totalFunctions !== 1 ? 's' : ''})`));
|
|
1822
|
+
}
|
|
1823
|
+
else {
|
|
1824
|
+
const parts = [];
|
|
1825
|
+
if (summary.componentsMissing > 0) {
|
|
1826
|
+
parts.push(`${summary.componentsMissing} component${summary.componentsMissing !== 1 ? 's' : ''} missing scenarios`);
|
|
1827
|
+
}
|
|
1828
|
+
if (summary.componentsWithErrors > 0) {
|
|
1829
|
+
parts.push(`${summary.componentsWithErrors} component${summary.componentsWithErrors !== 1 ? 's' : ''} with client errors`);
|
|
1830
|
+
}
|
|
1831
|
+
if (summary.functionsMissing > 0) {
|
|
1832
|
+
parts.push(`${summary.functionsMissing} function${summary.functionsMissing !== 1 ? 's' : ''} missing tests`);
|
|
1833
|
+
}
|
|
1834
|
+
if (summary.functionsFailing > 0) {
|
|
1835
|
+
parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
|
|
1836
|
+
}
|
|
1837
|
+
if (summary.functionsNameMismatch > 0) {
|
|
1838
|
+
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
1839
|
+
}
|
|
1840
|
+
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
1841
|
+
}
|
|
1842
|
+
console.log();
|
|
1843
|
+
if (!allOk) {
|
|
1844
|
+
process.exit(1);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
1848
|
+
async function handleScenarios() {
|
|
1849
|
+
const port = getServerPort();
|
|
1850
|
+
const url = `http://localhost:${port}/api/editor-scenarios`;
|
|
1851
|
+
let data;
|
|
1852
|
+
try {
|
|
1853
|
+
const res = await fetch(url);
|
|
1854
|
+
if (!res.ok) {
|
|
1855
|
+
console.error(chalk.red(`Error: Scenarios endpoint returned ${res.status}`));
|
|
1856
|
+
process.exit(1);
|
|
1857
|
+
}
|
|
1858
|
+
data = await res.json();
|
|
1859
|
+
}
|
|
1860
|
+
catch (err) {
|
|
1861
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
1862
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
1863
|
+
process.exit(1);
|
|
1864
|
+
}
|
|
1865
|
+
const scenarios = data.scenarios;
|
|
1866
|
+
if (scenarios.length === 0) {
|
|
1867
|
+
console.log();
|
|
1868
|
+
console.log(chalk.yellow('No scenarios found for this feature session.'));
|
|
1869
|
+
console.log();
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
// Group by componentName (null → "Uncategorized")
|
|
1873
|
+
const groups = new Map();
|
|
1874
|
+
for (const s of scenarios) {
|
|
1875
|
+
const key = s.componentName || 'Uncategorized';
|
|
1876
|
+
if (!groups.has(key))
|
|
1877
|
+
groups.set(key, []);
|
|
1878
|
+
groups.get(key).push(s);
|
|
1879
|
+
}
|
|
1880
|
+
// Strip component prefix from names (e.g. "DrinkCard - Default" → "Default")
|
|
1881
|
+
function stripPrefix(name, componentName) {
|
|
1882
|
+
if (!componentName)
|
|
1883
|
+
return name;
|
|
1884
|
+
const prefix = `${componentName} - `;
|
|
1885
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
1886
|
+
}
|
|
1887
|
+
// Calculate column widths
|
|
1888
|
+
let maxCompLen = 'Component'.length;
|
|
1889
|
+
let maxScenLen = 'Scenario'.length;
|
|
1890
|
+
for (const [comp, items] of groups) {
|
|
1891
|
+
maxCompLen = Math.max(maxCompLen, comp.length);
|
|
1892
|
+
for (const s of items) {
|
|
1893
|
+
const display = stripPrefix(s.name, s.componentName);
|
|
1894
|
+
maxScenLen = Math.max(maxScenLen, display.length);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
const pad = (str, len) => str + ' '.repeat(Math.max(0, len - str.length));
|
|
1898
|
+
// Build OSC 8 hyperlink (display length = visible text only)
|
|
1899
|
+
const osc8 = (url, text) => `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
|
|
1900
|
+
// Print table
|
|
1901
|
+
console.log();
|
|
1902
|
+
console.log(chalk.bold.cyan('━━━ Scenario Table ━━━'));
|
|
1903
|
+
console.log();
|
|
1904
|
+
const divComp = '─'.repeat(maxCompLen + 2);
|
|
1905
|
+
const divScen = '─'.repeat(maxScenLen + 2);
|
|
1906
|
+
// Header
|
|
1907
|
+
console.log(`┌${divComp}┬${divScen}┐`);
|
|
1908
|
+
console.log(`│ ${chalk.bold(pad('Component', maxCompLen))} │ ${chalk.bold(pad('Scenario', maxScenLen))} │`);
|
|
1909
|
+
console.log(`├${divComp}┼${divScen}┤`);
|
|
1910
|
+
// Rows
|
|
1911
|
+
let groupIndex = 0;
|
|
1912
|
+
for (const [comp, items] of groups) {
|
|
1913
|
+
if (groupIndex > 0) {
|
|
1914
|
+
console.log(`├${divComp}┼${divScen}┤`);
|
|
1915
|
+
}
|
|
1916
|
+
for (let i = 0; i < items.length; i++) {
|
|
1917
|
+
const s = items[i];
|
|
1918
|
+
const compCell = i === 0 ? chalk.bold(pad(comp, maxCompLen)) : pad('', maxCompLen);
|
|
1919
|
+
const display = stripPrefix(s.name, s.componentName);
|
|
1920
|
+
const linked = osc8(s.link, display);
|
|
1921
|
+
// linked has invisible escape chars; pad based on display length
|
|
1922
|
+
const scenPad = ' '.repeat(Math.max(0, maxScenLen - display.length));
|
|
1923
|
+
console.log(`│ ${compCell} │ ${linked}${scenPad} │`);
|
|
1924
|
+
}
|
|
1925
|
+
groupIndex++;
|
|
1926
|
+
}
|
|
1927
|
+
// Footer
|
|
1928
|
+
console.log(`└${divComp}┴${divScen}┘`);
|
|
1929
|
+
console.log();
|
|
1930
|
+
const total = scenarios.length;
|
|
1931
|
+
const groupCount = groups.size;
|
|
1932
|
+
console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
|
|
1933
|
+
console.log();
|
|
1934
|
+
}
|
|
1935
|
+
// ─── Template subcommand ─────────────────────────────────────────────
|
|
1936
|
+
async function handleTemplate() {
|
|
1937
|
+
const root = getProjectRoot();
|
|
1938
|
+
const port = getServerPort();
|
|
1939
|
+
if (hasProject(root)) {
|
|
1940
|
+
console.log(chalk.yellow('Project already exists (package.json found). Skipping.'));
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
// Determine which template to use from editor state
|
|
1944
|
+
const state = readState(root);
|
|
1945
|
+
const techStackId = state?.techStackId;
|
|
1946
|
+
const stack = techStackId
|
|
1947
|
+
? TECH_STACKS.find((s) => s.id === techStackId)
|
|
1948
|
+
: undefined;
|
|
1949
|
+
const templateName = stack?.templateDir || 'nextjs-prisma-sqlite';
|
|
1950
|
+
const templateDir = path.join(__dirname, '..', '..', 'templates', templateName);
|
|
1951
|
+
if (!fs.existsSync(templateDir)) {
|
|
1952
|
+
console.error(chalk.red('Error: Template directory not found at ' + templateDir));
|
|
1953
|
+
process.exit(1);
|
|
1954
|
+
}
|
|
1955
|
+
const { execSync } = await import('child_process');
|
|
1956
|
+
// 1. Copy template files
|
|
1957
|
+
console.log(chalk.bold(`Copying ${stack?.name || templateName} template files...`));
|
|
1958
|
+
execSync(`cp -r ${templateDir}/* .`, { cwd: root, stdio: 'inherit' });
|
|
1959
|
+
// Copy dotfiles that are stored without the dot prefix in templates
|
|
1960
|
+
const gitignorePath = path.join(templateDir, 'gitignore');
|
|
1961
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1962
|
+
execSync(`cp ${templateDir}/gitignore .gitignore`, {
|
|
1963
|
+
cwd: root,
|
|
1964
|
+
stdio: 'inherit',
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const envPath = path.join(templateDir, 'env');
|
|
1968
|
+
if (fs.existsSync(envPath)) {
|
|
1969
|
+
execSync(`cp ${templateDir}/env .env`, { cwd: root, stdio: 'inherit' });
|
|
1970
|
+
}
|
|
1971
|
+
// Copy seed adapter into .codeyam/ (stored outside .codeyam/ in the template
|
|
1972
|
+
// to avoid gitignore issues — .codeyam/ is selectively ignored in user projects)
|
|
1973
|
+
const seedAdapterSrc = path.join(templateDir, 'seed-adapter.ts');
|
|
1974
|
+
if (fs.existsSync(seedAdapterSrc)) {
|
|
1975
|
+
const codeyamDir = path.join(root, '.codeyam');
|
|
1976
|
+
if (!fs.existsSync(codeyamDir)) {
|
|
1977
|
+
fs.mkdirSync(codeyamDir, { recursive: true });
|
|
1978
|
+
}
|
|
1979
|
+
fs.copyFileSync(seedAdapterSrc, path.join(codeyamDir, 'seed-adapter.ts'));
|
|
1980
|
+
}
|
|
1981
|
+
console.log(chalk.green(' Template files copied.'));
|
|
1982
|
+
// 2. Install dependencies
|
|
1983
|
+
console.log(chalk.bold('Installing dependencies...'));
|
|
1984
|
+
execSync('npm install', { cwd: root, stdio: 'inherit' });
|
|
1985
|
+
console.log(chalk.green(' Dependencies installed.'));
|
|
1986
|
+
// 3. Initialize git
|
|
1987
|
+
const hasGit = fs.existsSync(path.join(root, '.git'));
|
|
1988
|
+
if (!hasGit) {
|
|
1989
|
+
console.log(chalk.bold('Initializing git...'));
|
|
1990
|
+
execSync('git init && git add -A && git commit -m "Initial commit"', {
|
|
1991
|
+
cwd: root,
|
|
1992
|
+
stdio: 'inherit',
|
|
1993
|
+
});
|
|
1994
|
+
console.log(chalk.green(' Git initialized.'));
|
|
1995
|
+
}
|
|
1996
|
+
// 4. Run codeyam init
|
|
1997
|
+
console.log(chalk.bold('Running codeyam init...'));
|
|
1998
|
+
await initCommand.handler({
|
|
1999
|
+
force: true,
|
|
2000
|
+
'keep-server': true,
|
|
2001
|
+
autoInit: false,
|
|
2002
|
+
$0: '',
|
|
2003
|
+
_: [],
|
|
2004
|
+
});
|
|
2005
|
+
console.log(chalk.green(' CodeYam initialized.'));
|
|
2006
|
+
// 5. Verify config has startCommand
|
|
2007
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
2008
|
+
if (fs.existsSync(configPath)) {
|
|
2009
|
+
try {
|
|
2010
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2011
|
+
const webapps = config.webapps || [];
|
|
2012
|
+
const hasStartCommand = webapps.some((w) => w.startCommand);
|
|
2013
|
+
if (!hasStartCommand) {
|
|
2014
|
+
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
2015
|
+
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
catch {
|
|
2019
|
+
// Config parse error is non-fatal
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
// 6. Trigger editor-refresh so the server picks up the new project
|
|
2023
|
+
console.log(chalk.bold('Refreshing editor...'));
|
|
2024
|
+
try {
|
|
2025
|
+
await fetch(`http://localhost:${port}/api/editor-refresh`, {
|
|
2026
|
+
method: 'POST',
|
|
2027
|
+
});
|
|
2028
|
+
console.log(chalk.green(' Editor refreshed.'));
|
|
2029
|
+
}
|
|
2030
|
+
catch {
|
|
2031
|
+
console.log(chalk.yellow(' Could not reach the CodeYam server for refresh. You may need to restart it.'));
|
|
2032
|
+
}
|
|
2033
|
+
console.log();
|
|
2034
|
+
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
2035
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
2036
|
+
}
|
|
2037
|
+
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
2038
|
+
/**
|
|
2039
|
+
* `codeyam editor sync`
|
|
2040
|
+
* Import scenarios from scenarios-manifest.json into the local database.
|
|
2041
|
+
*/
|
|
2042
|
+
async function handleSync() {
|
|
2043
|
+
const root = getProjectRoot();
|
|
2044
|
+
const manifest = readManifest(root);
|
|
2045
|
+
if (!manifest) {
|
|
2046
|
+
console.log(chalk.yellow('No scenarios-manifest.json found. Nothing to sync.'));
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
if (manifest.scenarios.length === 0) {
|
|
2050
|
+
console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
2054
|
+
let projectSlug = null;
|
|
2055
|
+
try {
|
|
2056
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2057
|
+
projectSlug = config.projectSlug || null;
|
|
2058
|
+
}
|
|
2059
|
+
catch {
|
|
2060
|
+
// fall through
|
|
2061
|
+
}
|
|
2062
|
+
if (!projectSlug) {
|
|
2063
|
+
console.error(chalk.red('Error: No project slug found. Run codeyam init first.'));
|
|
2064
|
+
process.exit(1);
|
|
2065
|
+
}
|
|
2066
|
+
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
2067
|
+
if (!connectionOk) {
|
|
2068
|
+
errorLog('Environment validation failed');
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
const { project } = await requireBranchAndProject(projectSlug);
|
|
2072
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2073
|
+
const db = getDatabase();
|
|
2074
|
+
// Fetch existing editor scenarios
|
|
2075
|
+
const existingRows = await db
|
|
2076
|
+
.selectFrom('editor_scenarios')
|
|
2077
|
+
.select(['id', 'updated_at'])
|
|
2078
|
+
.where('project_id', '=', project.id)
|
|
2079
|
+
.execute();
|
|
2080
|
+
const result = await syncManifestToDatabase(root, project.id, existingRows, async (row) => {
|
|
2081
|
+
await db
|
|
2082
|
+
.insertInto('editor_scenarios')
|
|
2083
|
+
.values(row)
|
|
2084
|
+
.execute();
|
|
2085
|
+
}, async (id, row) => {
|
|
2086
|
+
await db
|
|
2087
|
+
.updateTable('editor_scenarios')
|
|
2088
|
+
.set(row)
|
|
2089
|
+
.where('id', '=', id)
|
|
2090
|
+
.execute();
|
|
2091
|
+
});
|
|
2092
|
+
if (result.inserted === 0 && result.updated === 0) {
|
|
2093
|
+
console.log(chalk.dim('All scenarios up to date.'));
|
|
2094
|
+
}
|
|
2095
|
+
else {
|
|
2096
|
+
const parts = [];
|
|
2097
|
+
if (result.inserted > 0)
|
|
2098
|
+
parts.push(`${result.inserted} imported`);
|
|
2099
|
+
if (result.updated > 0)
|
|
2100
|
+
parts.push(`${result.updated} updated`);
|
|
2101
|
+
if (result.skipped > 0)
|
|
2102
|
+
parts.push(`${result.skipped} unchanged`);
|
|
2103
|
+
console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
// ─── Verify Images subcommand ─────────────────────────────────────────
|
|
2107
|
+
async function handleVerifyImages(jsonArg) {
|
|
2108
|
+
const port = getServerPort();
|
|
2109
|
+
const { parseVerifyImagesInput, extractImageUrls, resolveImageUrl, verifyImageUrls, buildVerifyImagesReport, extractImageUrlsFromScenarioFiles, } = await import('../utils/editorImageVerifier.js');
|
|
2110
|
+
let paths;
|
|
2111
|
+
let explicitImageUrls;
|
|
2112
|
+
try {
|
|
2113
|
+
const input = parseVerifyImagesInput(jsonArg);
|
|
2114
|
+
paths = input.paths;
|
|
2115
|
+
explicitImageUrls = input.imageUrls;
|
|
2116
|
+
}
|
|
2117
|
+
catch {
|
|
2118
|
+
console.error(chalk.red('Error: Invalid JSON argument'));
|
|
2119
|
+
process.exit(1);
|
|
2120
|
+
}
|
|
2121
|
+
// Get dev server URL
|
|
2122
|
+
let devServerUrl;
|
|
2123
|
+
try {
|
|
2124
|
+
const res = await fetch(`http://localhost:${port}/api/editor-dev-server`);
|
|
2125
|
+
const data = await res.json();
|
|
2126
|
+
devServerUrl = data.url || `http://localhost:${data.port || 3000}`;
|
|
2127
|
+
}
|
|
2128
|
+
catch {
|
|
2129
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2130
|
+
process.exit(1);
|
|
2131
|
+
}
|
|
2132
|
+
const allUrls = new Set();
|
|
2133
|
+
let pagesChecked = 0;
|
|
2134
|
+
for (const pagePath of paths) {
|
|
2135
|
+
const pageUrl = `${devServerUrl}${pagePath}`;
|
|
2136
|
+
try {
|
|
2137
|
+
const res = await fetch(pageUrl);
|
|
2138
|
+
if (!res.ok) {
|
|
2139
|
+
console.error(chalk.red(` ✗ ${pagePath} — HTTP ${res.status}`));
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
const html = await res.text();
|
|
2143
|
+
const imageUrls = extractImageUrls(html);
|
|
2144
|
+
for (const url of imageUrls) {
|
|
2145
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
2146
|
+
}
|
|
2147
|
+
pagesChecked++;
|
|
2148
|
+
}
|
|
2149
|
+
catch (err) {
|
|
2150
|
+
console.error(chalk.red(` ✗ ${pagePath} — ${err.message}`));
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
// Add explicitly-provided image URLs (Claude passes these directly)
|
|
2154
|
+
for (const url of explicitImageUrls) {
|
|
2155
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
2156
|
+
}
|
|
2157
|
+
// Also scan scenario mock data files for image URLs (client-rendered pages)
|
|
2158
|
+
const scenariosDir = path.join(getProjectRoot(), '.codeyam', 'editor-scenarios');
|
|
2159
|
+
const { urls: scenarioUrls, filesScanned: scenarioFilesScanned } = extractImageUrlsFromScenarioFiles(scenariosDir);
|
|
2160
|
+
for (const url of scenarioUrls) {
|
|
2161
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
2162
|
+
}
|
|
2163
|
+
const urlList = [...allUrls];
|
|
2164
|
+
const results = urlList.length > 0 ? await verifyImageUrls(urlList) : [];
|
|
2165
|
+
const report = buildVerifyImagesReport({
|
|
2166
|
+
pagesChecked,
|
|
2167
|
+
imageUrls: urlList,
|
|
2168
|
+
results,
|
|
2169
|
+
scenarioFilesScanned,
|
|
2170
|
+
});
|
|
2171
|
+
console.log(JSON.stringify(report, null, 2));
|
|
2172
|
+
if (report.failures.length > 0) {
|
|
2173
|
+
process.exit(1);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
// ─── Debug subcommand ────────────────────────────────────────────────
|
|
2177
|
+
function handleEditorDebug(args) {
|
|
2178
|
+
const root = getProjectRoot();
|
|
2179
|
+
const feature = args.feature || 'Sample Feature';
|
|
2180
|
+
const includeContext = args.includeContext !== false;
|
|
2181
|
+
let targets;
|
|
2182
|
+
try {
|
|
2183
|
+
targets = parseDebugTargets(args.target);
|
|
2184
|
+
}
|
|
2185
|
+
catch (err) {
|
|
2186
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2187
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2188
|
+
process.exit(1);
|
|
2189
|
+
}
|
|
2190
|
+
const includeResume = typeof args.resume === 'boolean' ? args.resume : true;
|
|
2191
|
+
const includeNormal = typeof args.resume === 'boolean' ? !args.resume : true;
|
|
2192
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
2193
|
+
const outputDir = args.output
|
|
2194
|
+
? path.resolve(args.output)
|
|
2195
|
+
: path.join(root, '.codeyam', 'logs', 'editor-debug', timestamp);
|
|
2196
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
2197
|
+
const contextEntries = includeContext
|
|
2198
|
+
? writeContextSnapshot(root, outputDir)
|
|
2199
|
+
: [];
|
|
2200
|
+
const scenarios = [];
|
|
2201
|
+
const wants = (id) => (targets ? targets.has(id) : true);
|
|
2202
|
+
if (wants('setup')) {
|
|
2203
|
+
scenarios.push({
|
|
2204
|
+
id: 'setup',
|
|
2205
|
+
title: 'Setup (no project)',
|
|
2206
|
+
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printSetup(tempRoot))),
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
if (wants('overview')) {
|
|
2210
|
+
scenarios.push({
|
|
2211
|
+
id: 'overview',
|
|
2212
|
+
title: 'Cycle overview (project, no state)',
|
|
2213
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, null))),
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
if (wants('overview-with-state')) {
|
|
2217
|
+
scenarios.push({
|
|
2218
|
+
id: 'overview-with-state',
|
|
2219
|
+
title: 'Cycle overview (project, with state)',
|
|
2220
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(4, feature)))),
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
const stepFns = {
|
|
2224
|
+
1: (r, f) => printStep1(r, f),
|
|
2225
|
+
2: printStep2,
|
|
2226
|
+
3: printStep3,
|
|
2227
|
+
4: printStep4,
|
|
2228
|
+
5: printStep5,
|
|
2229
|
+
6: printStep6,
|
|
2230
|
+
7: printStep7,
|
|
2231
|
+
8: printStep8,
|
|
2232
|
+
9: printStep9,
|
|
2233
|
+
10: printStep10,
|
|
2234
|
+
11: printStep11,
|
|
2235
|
+
12: printStep12,
|
|
2236
|
+
13: printStep13,
|
|
2237
|
+
};
|
|
2238
|
+
for (let step = 1; step <= 13; step++) {
|
|
2239
|
+
const stepId = `step-${step}`;
|
|
2240
|
+
if (!wants(stepId))
|
|
2241
|
+
continue;
|
|
2242
|
+
if (includeNormal) {
|
|
2243
|
+
scenarios.push({
|
|
2244
|
+
id: stepId,
|
|
2245
|
+
title: `Step ${step} (${STEP_LABELS[step]})`,
|
|
2246
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => stepFns[step](tempRoot, feature))),
|
|
2247
|
+
});
|
|
2248
|
+
if (step === 1 && !args.feature) {
|
|
2249
|
+
scenarios.push({
|
|
2250
|
+
id: 'step-1-no-feature',
|
|
2251
|
+
title: 'Step 1 (Plan) — no feature provided',
|
|
2252
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printStep1(tempRoot, undefined))),
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
if (step === 2) {
|
|
2256
|
+
scenarios.push({
|
|
2257
|
+
id: 'step-2-scaffold',
|
|
2258
|
+
title: 'Step 2 (Prototype) — scaffold flow (no project)',
|
|
2259
|
+
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if (includeResume) {
|
|
2264
|
+
scenarios.push({
|
|
2265
|
+
id: `${stepId}-resume`,
|
|
2266
|
+
title: `Step ${step} (${STEP_LABELS[step]}) — resuming`,
|
|
2267
|
+
render: () => withTempRoot(true, (tempRoot) => {
|
|
2268
|
+
writeState(tempRoot, makeState(step, feature));
|
|
2269
|
+
return captureOutput(() => stepFns[step](tempRoot, feature));
|
|
2270
|
+
}),
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
const indexLines = [
|
|
2275
|
+
'# CodeYam Editor Debug Bundle',
|
|
2276
|
+
'',
|
|
2277
|
+
`Generated: ${new Date().toISOString()}`,
|
|
2278
|
+
`Feature: "${feature}"`,
|
|
2279
|
+
`Resume variants: ${includeResume ? 'included' : 'skipped'}`,
|
|
2280
|
+
'Note: Output files preserve ANSI color codes (as printed to the terminal).',
|
|
2281
|
+
'',
|
|
2282
|
+
];
|
|
2283
|
+
if (includeContext) {
|
|
2284
|
+
indexLines.push('## Context snapshot');
|
|
2285
|
+
if (contextEntries.length === 0) {
|
|
2286
|
+
indexLines.push('- (none)');
|
|
2287
|
+
}
|
|
2288
|
+
else {
|
|
2289
|
+
for (const entry of contextEntries) {
|
|
2290
|
+
const source = entry.source ? ` ← ${entry.source}` : '';
|
|
2291
|
+
indexLines.push(`- ${entry.label}: ${entry.file} (${entry.status})${source}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
indexLines.push('');
|
|
2295
|
+
}
|
|
2296
|
+
indexLines.push('## Scenarios');
|
|
2297
|
+
for (const scenario of scenarios) {
|
|
2298
|
+
const fileName = `${scenario.id}.txt`;
|
|
2299
|
+
const output = scenario.render();
|
|
2300
|
+
fs.writeFileSync(path.join(outputDir, fileName), output, 'utf8');
|
|
2301
|
+
indexLines.push(`- ${scenario.title}: ${fileName}`);
|
|
2302
|
+
}
|
|
2303
|
+
fs.writeFileSync(path.join(outputDir, 'index.md'), indexLines.join('\n'), 'utf8');
|
|
2304
|
+
console.log();
|
|
2305
|
+
console.log(chalk.bold.cyan('━━━ Editor Debug Bundle ━━━'));
|
|
2306
|
+
console.log();
|
|
2307
|
+
console.log(chalk.green('Wrote debug bundle to: ') + chalk.bold(outputDir));
|
|
2308
|
+
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
2309
|
+
console.log();
|
|
2310
|
+
}
|
|
941
2311
|
// ─── Command definition ───────────────────────────────────────────────
|
|
942
2312
|
const editorCommand = {
|
|
943
2313
|
command: 'editor [step] [json]',
|
|
944
2314
|
describe: 'Editor mode guided workflow',
|
|
945
2315
|
builder: (yargs) => {
|
|
946
|
-
|
|
2316
|
+
const stepDescription = IS_INTERNAL_BUILD
|
|
2317
|
+
? 'Step number (1-13) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
|
|
2318
|
+
: 'Step number (1-13) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
|
|
2319
|
+
let builder = yargs
|
|
947
2320
|
.positional('step', {
|
|
948
2321
|
type: 'string',
|
|
949
|
-
describe:
|
|
2322
|
+
describe: stepDescription,
|
|
950
2323
|
})
|
|
951
2324
|
.positional('json', {
|
|
952
2325
|
type: 'string',
|
|
@@ -955,10 +2328,79 @@ const editorCommand = {
|
|
|
955
2328
|
.option('feature', {
|
|
956
2329
|
type: 'string',
|
|
957
2330
|
describe: 'Feature name (required for step 2)',
|
|
2331
|
+
})
|
|
2332
|
+
.option('app-formats', {
|
|
2333
|
+
type: 'string',
|
|
2334
|
+
describe: 'Comma-separated app formats (web-app, mobile-responsive-web-app, desktop-app, mobile-app)',
|
|
2335
|
+
})
|
|
2336
|
+
.option('tech-stack', {
|
|
2337
|
+
type: 'string',
|
|
2338
|
+
describe: 'Selected tech stack ID (e.g., nextjs-prisma-sqlite)',
|
|
2339
|
+
})
|
|
2340
|
+
.option('prompt', {
|
|
2341
|
+
type: 'string',
|
|
2342
|
+
describe: "User's original prompt (captured for journal/results)",
|
|
2343
|
+
})
|
|
2344
|
+
.option('port', {
|
|
2345
|
+
type: 'number',
|
|
2346
|
+
alias: 'p',
|
|
2347
|
+
describe: 'Port to run the web server on',
|
|
2348
|
+
default: 3111,
|
|
958
2349
|
});
|
|
2350
|
+
if (IS_INTERNAL_BUILD) {
|
|
2351
|
+
builder = builder
|
|
2352
|
+
.option('target', {
|
|
2353
|
+
type: 'string',
|
|
2354
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-13, or comma-separated list)',
|
|
2355
|
+
})
|
|
2356
|
+
.option('resume', {
|
|
2357
|
+
type: 'boolean',
|
|
2358
|
+
describe: 'Debug: only render resume variants (use --no-resume for only normal)',
|
|
2359
|
+
})
|
|
2360
|
+
.option('include-context', {
|
|
2361
|
+
type: 'boolean',
|
|
2362
|
+
default: true,
|
|
2363
|
+
describe: 'Debug: include CLAUDE.md, skill, and editor-mode-context snapshots',
|
|
2364
|
+
})
|
|
2365
|
+
.option('output', {
|
|
2366
|
+
type: 'string',
|
|
2367
|
+
describe: 'Debug: output directory for the bundle',
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
return builder;
|
|
959
2371
|
},
|
|
960
2372
|
handler: async (argv) => {
|
|
961
2373
|
const root = getProjectRoot();
|
|
2374
|
+
// API subcommands: preview, show-results, hide-results, commit,
|
|
2375
|
+
// journal, journal-update, dev-server, client-errors
|
|
2376
|
+
if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
|
|
2377
|
+
const port = getServerPort();
|
|
2378
|
+
try {
|
|
2379
|
+
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
2380
|
+
const result = await callEditorApi(request, port);
|
|
2381
|
+
if (!result.ok) {
|
|
2382
|
+
console.error(chalk.red(`Error: ${request.endpoint} returned ${result.status}`));
|
|
2383
|
+
if (result.data)
|
|
2384
|
+
console.error(JSON.stringify(result.data, null, 2));
|
|
2385
|
+
process.exit(1);
|
|
2386
|
+
}
|
|
2387
|
+
if (result.data != null) {
|
|
2388
|
+
const summary = formatApiSubcommandResult(argv.step, result.data);
|
|
2389
|
+
if (summary) {
|
|
2390
|
+
console.log(summary);
|
|
2391
|
+
}
|
|
2392
|
+
else {
|
|
2393
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
catch (err) {
|
|
2398
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2399
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
2400
|
+
process.exit(1);
|
|
2401
|
+
}
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
962
2404
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
963
2405
|
if (argv.step === 'register') {
|
|
964
2406
|
await handleRegister(argv.json || '');
|
|
@@ -969,27 +2411,259 @@ const editorCommand = {
|
|
|
969
2411
|
await handleAnalyzeImports();
|
|
970
2412
|
return;
|
|
971
2413
|
}
|
|
972
|
-
|
|
973
|
-
if (step
|
|
974
|
-
|
|
975
|
-
|
|
2414
|
+
// Subcommand: codeyam editor dependents <EntityName>
|
|
2415
|
+
if (argv.step === 'dependents') {
|
|
2416
|
+
await handleDependents(argv.json || '');
|
|
2417
|
+
return;
|
|
976
2418
|
}
|
|
977
|
-
|
|
978
|
-
|
|
2419
|
+
// Subcommand: codeyam editor audit
|
|
2420
|
+
if (argv.step === 'audit') {
|
|
2421
|
+
await handleAudit();
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
// Subcommand: codeyam editor scenarios
|
|
2425
|
+
if (argv.step === 'scenarios') {
|
|
2426
|
+
await handleScenarios();
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
// Subcommand: codeyam editor change <feature>
|
|
2430
|
+
if (argv.step === 'change') {
|
|
2431
|
+
handleChange(argv.json || '');
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
// Subcommand: codeyam editor verify-images '{"paths":["/","/drinks/1"]}'
|
|
2435
|
+
if (argv.step === 'verify-images') {
|
|
2436
|
+
await handleVerifyImages(argv.json || '');
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
// Subcommand: codeyam editor validate-seed '{"products":[...]}'
|
|
2440
|
+
if (argv.step === 'validate-seed') {
|
|
2441
|
+
await handleValidateSeed(argv.json || '');
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
// Subcommand: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
2445
|
+
if (argv.step === 'isolate') {
|
|
2446
|
+
const names = [];
|
|
2447
|
+
if (argv.json)
|
|
2448
|
+
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
2449
|
+
// Collect any extra positional args (yargs puts them in argv._)
|
|
2450
|
+
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
2451
|
+
names.push(...extras);
|
|
2452
|
+
handleIsolate(names);
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
// Subcommand: codeyam editor template — scaffold project from template
|
|
2456
|
+
if (argv.step === 'template') {
|
|
2457
|
+
await handleTemplate();
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
// Subcommand: codeyam editor sync — import scenarios from manifest
|
|
2461
|
+
if (argv.step === 'sync') {
|
|
2462
|
+
await handleSync();
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
// Subcommand: codeyam editor debug [--target ...]
|
|
2466
|
+
if (argv.step === 'debug') {
|
|
2467
|
+
if (!IS_INTERNAL_BUILD) {
|
|
2468
|
+
console.error(chalk.red('Error: "codeyam editor debug" is internal-only.'));
|
|
2469
|
+
process.exit(1);
|
|
2470
|
+
}
|
|
2471
|
+
await handleEditorDebug(argv);
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
// Subcommand: codeyam editor steps — show setup or cycle overview
|
|
2475
|
+
if (argv.step === 'steps') {
|
|
979
2476
|
if (!hasProject(root)) {
|
|
980
2477
|
printSetup(root);
|
|
981
2478
|
}
|
|
982
2479
|
else {
|
|
983
2480
|
const state = readState(root);
|
|
2481
|
+
// Clear prompt file when feature is done (step 13) so the hook
|
|
2482
|
+
// can capture the next feature request from the user.
|
|
2483
|
+
if (state?.step === 13) {
|
|
2484
|
+
clearEditorUserPrompt(root);
|
|
2485
|
+
}
|
|
984
2486
|
printCycleOverview(root, state);
|
|
985
2487
|
}
|
|
986
2488
|
return;
|
|
987
2489
|
}
|
|
2490
|
+
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
2491
|
+
if (step != null && (isNaN(step) || step < 1 || step > 13)) {
|
|
2492
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-13.`));
|
|
2493
|
+
process.exit(1);
|
|
2494
|
+
}
|
|
2495
|
+
if (step == null) {
|
|
2496
|
+
// No step specified: launch editor server + open browser
|
|
2497
|
+
// Auto-init if needed
|
|
2498
|
+
let projectRoot = getStateProjectRoot();
|
|
2499
|
+
if (!projectRoot) {
|
|
2500
|
+
await initCommand.handler({
|
|
2501
|
+
force: false,
|
|
2502
|
+
autoInit: true,
|
|
2503
|
+
$0: '',
|
|
2504
|
+
_: [],
|
|
2505
|
+
});
|
|
2506
|
+
projectRoot = getStateProjectRoot();
|
|
2507
|
+
if (!projectRoot) {
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
const configPath = path.join(projectRoot, '.codeyam', 'config.json');
|
|
2512
|
+
try {
|
|
2513
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2514
|
+
const { projectSlug } = config;
|
|
2515
|
+
if (!projectSlug) {
|
|
2516
|
+
errorLog('Missing project slug. Try reinitializing with: codeyam init --force');
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
2520
|
+
if (!connectionOk) {
|
|
2521
|
+
errorLog('Environment validation failed');
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
2525
|
+
// Auto-sync scenarios from manifest if it exists
|
|
2526
|
+
const manifestData = readManifest(projectRoot);
|
|
2527
|
+
if (manifestData && manifestData.scenarios.length > 0) {
|
|
2528
|
+
try {
|
|
2529
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2530
|
+
const db = getDatabase();
|
|
2531
|
+
const existingRows = await db
|
|
2532
|
+
.selectFrom('editor_scenarios')
|
|
2533
|
+
.select(['id', 'updated_at'])
|
|
2534
|
+
.where('project_id', '=', project.id)
|
|
2535
|
+
.execute();
|
|
2536
|
+
const syncResult = await syncManifestToDatabase(projectRoot, project.id, existingRows, async (row) => {
|
|
2537
|
+
await db
|
|
2538
|
+
.insertInto('editor_scenarios')
|
|
2539
|
+
.values(row)
|
|
2540
|
+
.execute();
|
|
2541
|
+
}, async (id, row) => {
|
|
2542
|
+
await db
|
|
2543
|
+
.updateTable('editor_scenarios')
|
|
2544
|
+
.set(row)
|
|
2545
|
+
.where('id', '=', id)
|
|
2546
|
+
.execute();
|
|
2547
|
+
});
|
|
2548
|
+
if (syncResult.inserted > 0 || syncResult.updated > 0) {
|
|
2549
|
+
const parts = [];
|
|
2550
|
+
if (syncResult.inserted > 0)
|
|
2551
|
+
parts.push(`${syncResult.inserted} imported`);
|
|
2552
|
+
if (syncResult.updated > 0)
|
|
2553
|
+
parts.push(`${syncResult.updated} updated`);
|
|
2554
|
+
console.log(chalk.green(` Synced scenarios from manifest: ${parts.join(', ')}`));
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch {
|
|
2558
|
+
// Non-fatal — sync failure shouldn't block editor startup
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
// `codeyam editor` (no step) always implies editor mode.
|
|
2562
|
+
// The empty-folder heuristic is no longer needed here — running this
|
|
2563
|
+
// command IS the signal. We still detect empty folders so that
|
|
2564
|
+
// the very first `codeyam editor` in a bare directory works before
|
|
2565
|
+
// metadata has been persisted.
|
|
2566
|
+
const editorMode = true;
|
|
2567
|
+
if (editorMode && !project.metadata?.labs?.simulations) {
|
|
2568
|
+
try {
|
|
2569
|
+
await updateProjectMetadata({
|
|
2570
|
+
projectSlug,
|
|
2571
|
+
metadataUpdate: {
|
|
2572
|
+
editorMode: true,
|
|
2573
|
+
labs: { simulations: true },
|
|
2574
|
+
},
|
|
2575
|
+
});
|
|
2576
|
+
const refreshed = await requireBranchAndProject(projectSlug);
|
|
2577
|
+
Object.assign(project, refreshed.project);
|
|
2578
|
+
}
|
|
2579
|
+
catch {
|
|
2580
|
+
// Non-fatal
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
// Install skills/hooks
|
|
2584
|
+
const simulationsEnabled = project.metadata?.labs?.simulations ?? false;
|
|
2585
|
+
const skillMode = simulationsEnabled
|
|
2586
|
+
? 'full'
|
|
2587
|
+
: editorMode
|
|
2588
|
+
? 'editor'
|
|
2589
|
+
: 'memory';
|
|
2590
|
+
await installClaudeCodeSkills(projectRoot, {
|
|
2591
|
+
mode: skillMode,
|
|
2592
|
+
editorMode,
|
|
2593
|
+
});
|
|
2594
|
+
setupClaudeCodeSettings(projectRoot, {
|
|
2595
|
+
mode: skillMode,
|
|
2596
|
+
editorMode,
|
|
2597
|
+
});
|
|
2598
|
+
// Auto-finalize analyzer so codeyam analyze works
|
|
2599
|
+
if (editorMode && !isAnalyzerFinalized()) {
|
|
2600
|
+
try {
|
|
2601
|
+
const { execSync } = await import('child_process');
|
|
2602
|
+
const templatePath = getAnalyzerTemplatePath();
|
|
2603
|
+
if (fs.existsSync(templatePath)) {
|
|
2604
|
+
console.log(' Setting up simulations (first time only)...');
|
|
2605
|
+
execSync('npm install --include=dev', {
|
|
2606
|
+
cwd: templatePath,
|
|
2607
|
+
stdio: 'pipe',
|
|
2608
|
+
});
|
|
2609
|
+
execSync('npx playwright install chromium', {
|
|
2610
|
+
cwd: templatePath,
|
|
2611
|
+
stdio: 'pipe',
|
|
2612
|
+
});
|
|
2613
|
+
execSync('npm run build', {
|
|
2614
|
+
cwd: templatePath,
|
|
2615
|
+
stdio: 'pipe',
|
|
2616
|
+
});
|
|
2617
|
+
fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
catch {
|
|
2621
|
+
// Non-fatal
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
// Start background server
|
|
2625
|
+
const { url } = await startBackgroundServer({
|
|
2626
|
+
port: argv.port || 3111,
|
|
2627
|
+
rootPath: projectRoot,
|
|
2628
|
+
project,
|
|
2629
|
+
branch,
|
|
2630
|
+
});
|
|
2631
|
+
console.log();
|
|
2632
|
+
console.log(` Dashboard: ${url}`);
|
|
2633
|
+
console.log(' Run "codeyam --help" for all commands');
|
|
2634
|
+
console.log(chalk.bold(' Run "codeyam editor" to launch the editor at any time'));
|
|
2635
|
+
console.log();
|
|
2636
|
+
// Open browser to /editor
|
|
2637
|
+
try {
|
|
2638
|
+
const { execSync } = await import('child_process');
|
|
2639
|
+
const openCommand = process.platform === 'darwin'
|
|
2640
|
+
? 'open'
|
|
2641
|
+
: process.platform === 'win32'
|
|
2642
|
+
? 'start ""'
|
|
2643
|
+
: 'xdg-open';
|
|
2644
|
+
execSync(`${openCommand} "${url}/editor"`, { stdio: 'ignore' });
|
|
2645
|
+
}
|
|
2646
|
+
catch {
|
|
2647
|
+
// Silently fail if open command doesn't work
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
catch (err) {
|
|
2651
|
+
errorLog('Failed to start CodeYam editor server');
|
|
2652
|
+
errorLog(err);
|
|
2653
|
+
process.exit(1);
|
|
2654
|
+
}
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
988
2657
|
const state = readState(root);
|
|
989
2658
|
switch (step) {
|
|
990
2659
|
case 1: {
|
|
991
2660
|
const feature = argv.feature || undefined;
|
|
992
|
-
|
|
2661
|
+
const appFormats = argv['app-formats']
|
|
2662
|
+
? argv['app-formats'].split(',').map((f) => f.trim())
|
|
2663
|
+
: undefined;
|
|
2664
|
+
const techStackId = argv['tech-stack'] || undefined;
|
|
2665
|
+
const prompt = argv.prompt || undefined;
|
|
2666
|
+
printStep1(root, feature, { appFormats, techStackId }, prompt);
|
|
993
2667
|
break;
|
|
994
2668
|
}
|
|
995
2669
|
case 2: {
|
|
@@ -1010,7 +2684,9 @@ const editorCommand = {
|
|
|
1010
2684
|
case 8:
|
|
1011
2685
|
case 9:
|
|
1012
2686
|
case 10:
|
|
1013
|
-
case 11:
|
|
2687
|
+
case 11:
|
|
2688
|
+
case 12:
|
|
2689
|
+
case 13: {
|
|
1014
2690
|
const feature = argv.feature || state?.feature;
|
|
1015
2691
|
if (!feature) {
|
|
1016
2692
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
@@ -1026,6 +2702,8 @@ const editorCommand = {
|
|
|
1026
2702
|
9: printStep9,
|
|
1027
2703
|
10: printStep10,
|
|
1028
2704
|
11: printStep11,
|
|
2705
|
+
12: printStep12,
|
|
2706
|
+
13: printStep13,
|
|
1029
2707
|
};
|
|
1030
2708
|
stepFns[step](root, feature);
|
|
1031
2709
|
break;
|