@codeyam/codeyam-cli 0.1.0-staging.8778565 → 0.1.0-staging.87dd4be
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +8 -8
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +2 -2
- package/analyzer-template/packages/ai/package.json +1 -1
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +82 -0
- package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
- package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +5 -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 +84 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/cli.js +9 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +56 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +2369 -346
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
- package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
- package/codeyam-cli/src/commands/init.js +69 -34
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/commands/telemetry.js +37 -0
- package/codeyam-cli/src/commands/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2046 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js +76 -0
- package/codeyam-cli/src/utils/__tests__/editorBroadcastViewport.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js +100 -0
- package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +76 -3
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +381 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +202 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorMigration.test.js +435 -0
- package/codeyam-cli/src/utils/__tests__/editorMigration.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +88 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +47 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +70 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +1335 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
- package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
- package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +363 -11
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +30 -2
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
- package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +284 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +649 -223
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +1 -0
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +3 -1
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +9 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
- package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
- package/codeyam-cli/src/utils/backgroundServer.js +3 -9
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/database.js +37 -2
- package/codeyam-cli/src/utils/database.js.map +1 -1
- package/codeyam-cli/src/utils/editorApi.js +11 -5
- package/codeyam-cli/src/utils/editorApi.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +372 -5
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
- package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
- package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
- package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/editorEntityHelpers.js +144 -0
- package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +72 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorMigration.js +224 -0
- package/codeyam-cli/src/utils/editorMigration.js.map +1 -0
- package/codeyam-cli/src/utils/editorPreview.js +31 -0
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorRecapture.js +109 -0
- package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +24 -2
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +458 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +253 -4
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
- package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
- package/codeyam-cli/src/utils/entityChangeStatus.js +53 -6
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +41 -3
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/fileWatcher.js +38 -0
- package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
- package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
- package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +14 -0
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -1
- package/codeyam-cli/src/utils/progress.js +2 -2
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/routePatternMatching.js +129 -0
- package/codeyam-cli/src/utils/routePatternMatching.js.map +1 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js +77 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +269 -74
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -0
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/slugUtils.js +25 -0
- package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
- package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
- package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/telemetry.js +106 -0
- package/codeyam-cli/src/utils/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +80 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +218 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +217 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +71 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
- package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
- package/codeyam-cli/src/webserver/app/types/editor.js +8 -0
- package/codeyam-cli/src/webserver/app/types/editor.js.map +1 -0
- package/codeyam-cli/src/webserver/backgroundServer.js +60 -61
- package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CLe80MMu.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BcgbViKV.js → EntityItem-Crt_KN_U.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CQIG2qda.js → EntityTypeIcon-CD7lGABo.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-CgTNOhnu.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CKeQT5Ty.js +25 -0
- package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-D3s1MFkb.js +3 -0
- package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-BU_OAEMP.js → LoadingDots-By5zI316.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-ceAyBX-H.js → LogViewer-CM5zg40N.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzHcG7SE.js → ReportIssueModal-C2PLkej3.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-DanvyBPb.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-0DY_NKil.js → ScenarioViewer-DUMfcNVK.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +34 -0
- package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-CK7-NaPZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-BA_Ry-rs.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{_index-DLxKhri3.js → _index-BAWd-Xjf.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BcY3q6nt.js → activity.(_tab)-BOARiB-g.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-CHx25PAe.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bni3iiUj.js → agent-transcripts-Bg3e7q4S.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-rename-scenario-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-seed-state-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-prompt-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-BYOypzCa.js → book-open-CL-lMgHh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-C_Pmso5S.js → chevron-down-GmAjGS9-.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BVMi9VA5.js → circle-check-DFcQkN5j.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-n2FB0_Sw.js → copy-C6iF61Xs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-CC6AbExI.js → createLucideIcon-4ImjHTVC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-C8y4mmyv.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-Gbk_i5Js.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Bnx7yUP0.js +58 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BF4oLwaE.js → entity._sha._-Blfy9UlN.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-DQM8E7L4.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-CAoXLsQr.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-SuW9syRS.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-Daa96Fr1.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/files-D-xGrg29.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/git-Bq_fbXP5.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-fAqOD9ex.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-Bp1l4hSv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-CWV9XZiG.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-DE3jI_dv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/jsx-runtime-D_zvdyIk.js +9 -0
- package/codeyam-cli/src/webserver/build/client/assets/labs-B_IX45ih.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DaAZ_H2w.js → loader-circle-De-7qQ2u.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-b9d4d267.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/memory-Cx2xEx7s.js +101 -0
- package/codeyam-cli/src/webserver/build/client/assets/{pause-f5-1lKBt.js → pause-CFxEKL1u.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-DB3O9_9j.js +67 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-Di64LWVb.js → search-BdBb5aqc.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/settings-DdE-Untf.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/simulations-DSCdE99u.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-Br7MOqts.js → terminal-CrplD4b1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BLdiCuG-.js → triangle-alert-DqJ0j69l.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-DhXHbEjP.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BNd5hYuW.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useReportContext-Cy5Qg_UR.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/useToast-5HR2j9ZE.js +1 -0
- package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-CGwTN3V2.js +13 -0
- package/codeyam-cli/src/webserver/build/server/assets/index-D4meMKy3.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/init-odGJ_c2-.js +10 -0
- package/codeyam-cli/src/webserver/build/server/assets/progress-CHTtrxFG.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-TmPfF7pT.js +552 -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 +208 -17
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +106 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
- package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
- package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
- package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +36 -0
- package/codeyam-cli/src/webserver/server.js +87 -4
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +140 -35
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +1 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
- package/codeyam-cli/templates/codeyam-editor-reference.md +214 -0
- package/codeyam-cli/templates/editor-step-hook.py +114 -24
- package/codeyam-cli/templates/expo-react-native/README.md +41 -0
- package/codeyam-cli/templates/expo-react-native/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +14 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +2 -1
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -7
- package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +2 -1
- package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
- package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +76 -10
- package/package.json +2 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +84 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/database/src/lib/loadEntities.js +0 -6
- package/packages/database/src/lib/loadEntities.js.map +1 -1
- package/packages/database/src/lib/updateCommitMetadata.js +0 -25
- package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-BPXZwM4t.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
- package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
- package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-C4pqxYJB.js +0 -51
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Csi0_PMl.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-DgN1LTTt.js +0 -10
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-BLQMSKZa.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-C7YX6r3H.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/files-BZrlFE1F.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-BkWJ_UNc.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/labs-Zk7ryIM1.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-c26eb85b.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-Bl2rpw8u.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/root-ClvYBUSA.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/settings-0OrEMU6J.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/simulations-DWT-CvLy.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useReportContext-O-jkvSPx.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useToast-9FIWuYfK.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/index-DflIr5SD.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-OhKy839M.js +0 -416
|
@@ -11,15 +11,18 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
|
|
|
11
11
|
import { startBackgroundServer } from "../utils/backgroundServer.js";
|
|
12
12
|
import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
|
-
import {
|
|
14
|
+
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
15
|
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
17
|
import initCommand from "./init.js";
|
|
18
|
-
import {
|
|
19
|
-
import { clearEditorState, clearEditorUserPrompt, } from "../utils/editorScenarios.js";
|
|
20
|
-
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
18
|
+
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
|
+
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
21
22
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
23
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
24
|
+
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
25
|
+
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
23
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
27
|
const __dirname = path.dirname(__filename);
|
|
25
28
|
const STEP_LABELS = {
|
|
@@ -36,6 +39,21 @@ const STEP_LABELS = {
|
|
|
36
39
|
11: 'Journal',
|
|
37
40
|
12: 'Review',
|
|
38
41
|
13: 'Present',
|
|
42
|
+
14: 'Commit',
|
|
43
|
+
15: 'Finalize',
|
|
44
|
+
16: 'Push',
|
|
45
|
+
};
|
|
46
|
+
const MIGRATION_STEP_LABELS = {
|
|
47
|
+
1: 'Survey',
|
|
48
|
+
2: 'App Scenarios',
|
|
49
|
+
3: 'Component Scenarios',
|
|
50
|
+
4: 'Preview',
|
|
51
|
+
5: 'Discuss',
|
|
52
|
+
6: 'Decompose',
|
|
53
|
+
7: 'Extract',
|
|
54
|
+
8: 'Recapture',
|
|
55
|
+
9: 'Journal',
|
|
56
|
+
10: 'Present',
|
|
39
57
|
};
|
|
40
58
|
/**
|
|
41
59
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
@@ -56,6 +74,18 @@ function logEvent(root, event, data) {
|
|
|
56
74
|
// Logging is best-effort
|
|
57
75
|
}
|
|
58
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Read the design system file if it exists.
|
|
79
|
+
*/
|
|
80
|
+
function readDesignSystem(root) {
|
|
81
|
+
const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
|
|
82
|
+
try {
|
|
83
|
+
return fs.readFileSync(designSystemPath, 'utf8');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
59
89
|
/**
|
|
60
90
|
* Get the project root (where .codeyam/ lives) or cwd.
|
|
61
91
|
*/
|
|
@@ -128,6 +158,39 @@ function getServerPort() {
|
|
|
128
158
|
}
|
|
129
159
|
return process.env.CODEYAM_PORT || '3111';
|
|
130
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Read the project's default dimension name and available screen size names.
|
|
163
|
+
* Used by step instructions to show project-specific dimension examples
|
|
164
|
+
* instead of hardcoded "Desktop".
|
|
165
|
+
*/
|
|
166
|
+
function getProjectDimensions(root) {
|
|
167
|
+
try {
|
|
168
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
169
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
170
|
+
const defaultName = config.defaultScreenSize?.name ||
|
|
171
|
+
(config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
|
|
172
|
+
'Desktop';
|
|
173
|
+
const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
|
|
174
|
+
return { defaultName, names };
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { defaultName: 'Desktop', names: [] };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Print dimension guidance when the project has multiple screen sizes.
|
|
182
|
+
* Tells Claude to pick the right dimension for the content being previewed
|
|
183
|
+
* instead of blindly using the default for everything.
|
|
184
|
+
*/
|
|
185
|
+
function printDimensionGuidance(defaultName, names) {
|
|
186
|
+
if (names.length <= 1)
|
|
187
|
+
return;
|
|
188
|
+
// Find a non-default dimension to suggest as the "other" option
|
|
189
|
+
const otherName = names.find((n) => n !== defaultName) || names[1];
|
|
190
|
+
console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
|
|
191
|
+
console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
|
|
192
|
+
console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
|
|
193
|
+
}
|
|
131
194
|
/**
|
|
132
195
|
* Print a checklist item.
|
|
133
196
|
* Inline backtick-wrapped text is highlighted in cyan for visibility.
|
|
@@ -137,6 +200,156 @@ function checkbox(text) {
|
|
|
137
200
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
138
201
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
139
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
205
|
+
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
206
|
+
*/
|
|
207
|
+
function printAppScenarioInstructions(pageName, route) {
|
|
208
|
+
const root = process.cwd();
|
|
209
|
+
const hasSeedAdapter = !!detectSeedAdapter(root);
|
|
210
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
211
|
+
console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
|
|
212
|
+
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
213
|
+
console.log();
|
|
214
|
+
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
215
|
+
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
216
|
+
console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
|
|
217
|
+
console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
|
|
218
|
+
console.log();
|
|
219
|
+
checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
|
|
220
|
+
console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
|
|
221
|
+
console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
|
|
222
|
+
if (pageName) {
|
|
223
|
+
console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
227
|
+
}
|
|
228
|
+
console.log();
|
|
229
|
+
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
230
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
231
|
+
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
232
|
+
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
233
|
+
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
234
|
+
console.log();
|
|
235
|
+
if (hasSeedAdapter) {
|
|
236
|
+
checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data — without it the page will be empty!'));
|
|
237
|
+
console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
|
|
238
|
+
console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
|
|
239
|
+
console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
|
|
240
|
+
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
244
|
+
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
245
|
+
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
246
|
+
}
|
|
247
|
+
checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
|
|
248
|
+
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
249
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
250
|
+
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
251
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
252
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
253
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Shared glossary instructions used by editor Step 6 and migration Step 8.
|
|
257
|
+
* Prints format, required fields, and example.
|
|
258
|
+
*/
|
|
259
|
+
function printGlossaryInstructions(feature) {
|
|
260
|
+
console.log(chalk.bold.yellow('IMPORTANT: glossary.json is a plain JSON array — NOT an object wrapper.'));
|
|
261
|
+
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
262
|
+
checkbox('Add an entry for each new function/component extracted');
|
|
263
|
+
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
264
|
+
checkbox('For each function with a test file, set `testFile` to the relative path');
|
|
265
|
+
console.log();
|
|
266
|
+
console.log(chalk.bold('File format:'));
|
|
267
|
+
console.log(chalk.dim(' ['));
|
|
268
|
+
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
269
|
+
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
270
|
+
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
271
|
+
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
272
|
+
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
273
|
+
console.log(chalk.dim(` "feature": "${feature || '...'}", "createdAt": "..." }`));
|
|
274
|
+
console.log(chalk.dim(' ]'));
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Shared extraction plan instructions used by editor Step 4 and migration Step 6.
|
|
278
|
+
* Prints THE RULE, extractable categories, plan format requirements.
|
|
279
|
+
*/
|
|
280
|
+
function printExtractionPlanInstructions() {
|
|
281
|
+
console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
|
|
282
|
+
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
283
|
+
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
284
|
+
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
285
|
+
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(chalk.bold('Checklist:'));
|
|
288
|
+
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
289
|
+
checkbox('Read EVERY file used by this page/feature');
|
|
290
|
+
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
291
|
+
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
292
|
+
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
293
|
+
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
294
|
+
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
295
|
+
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
296
|
+
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
297
|
+
console.log();
|
|
298
|
+
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
299
|
+
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
300
|
+
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
301
|
+
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
302
|
+
console.log();
|
|
303
|
+
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
304
|
+
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
305
|
+
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
306
|
+
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
307
|
+
console.log();
|
|
308
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
312
|
+
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
313
|
+
*/
|
|
314
|
+
function printComponentCaptureInstructions() {
|
|
315
|
+
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
316
|
+
console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
|
|
317
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
318
|
+
checkbox('For each visual component:');
|
|
319
|
+
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
320
|
+
console.log(chalk.dim(' — Props/interface'));
|
|
321
|
+
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
322
|
+
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
323
|
+
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
324
|
+
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
325
|
+
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
326
|
+
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
327
|
+
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
328
|
+
console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
|
|
329
|
+
console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
|
|
330
|
+
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
331
|
+
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
332
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
333
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
334
|
+
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
335
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
336
|
+
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
337
|
+
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
338
|
+
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
339
|
+
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
340
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
341
|
+
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
342
|
+
console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
|
|
343
|
+
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
344
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
|
|
345
|
+
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
346
|
+
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
347
|
+
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
348
|
+
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
349
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
350
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
351
|
+
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
352
|
+
}
|
|
140
353
|
/**
|
|
141
354
|
* Print a section header.
|
|
142
355
|
*/
|
|
@@ -149,13 +362,13 @@ function stepHeader(step, title, feature) {
|
|
|
149
362
|
console.log();
|
|
150
363
|
}
|
|
151
364
|
/**
|
|
152
|
-
* Print a colored progress tracker showing all
|
|
365
|
+
* Print a colored progress tracker showing all 16 steps.
|
|
153
366
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
154
367
|
*/
|
|
155
368
|
function printProgressTracker(current) {
|
|
156
369
|
console.log();
|
|
157
|
-
console.log(chalk.dim('┌─────────────────────────────────────┐'));
|
|
158
|
-
for (let i = 1; i <=
|
|
370
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
371
|
+
for (let i = 1; i <= 16; i++) {
|
|
159
372
|
const label = STEP_LABELS[i];
|
|
160
373
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
161
374
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -193,7 +406,7 @@ function stopGate(current, opts) {
|
|
|
193
406
|
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
194
407
|
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
195
408
|
console.log();
|
|
196
|
-
if (current <
|
|
409
|
+
if (current < 16) {
|
|
197
410
|
console.log(chalk.green('When done, run: ') +
|
|
198
411
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
199
412
|
}
|
|
@@ -237,6 +450,14 @@ function printResumptionHeader(step) {
|
|
|
237
450
|
],
|
|
238
451
|
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
239
452
|
13: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
453
|
+
14: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
454
|
+
15: [
|
|
455
|
+
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
456
|
+
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
457
|
+
],
|
|
458
|
+
16: [
|
|
459
|
+
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
460
|
+
],
|
|
240
461
|
};
|
|
241
462
|
const label = STEP_LABELS[step] || 'Unknown';
|
|
242
463
|
console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
|
|
@@ -341,7 +562,7 @@ function parseDebugTargets(target) {
|
|
|
341
562
|
}
|
|
342
563
|
if (entry.startsWith('step-')) {
|
|
343
564
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
344
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
565
|
+
if (!isNaN(num) && num >= 1 && num <= 16) {
|
|
345
566
|
valid.add(`step-${num}`);
|
|
346
567
|
continue;
|
|
347
568
|
}
|
|
@@ -425,6 +646,15 @@ function printSetup(root) {
|
|
|
425
646
|
console.log();
|
|
426
647
|
console.log("No project detected. Let's get started.");
|
|
427
648
|
console.log();
|
|
649
|
+
// ── Design System ────────────────────────────────────────────────
|
|
650
|
+
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
651
|
+
console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
|
|
652
|
+
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
653
|
+
console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
|
|
654
|
+
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
655
|
+
console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
|
|
656
|
+
console.log(chalk.dim(' → Skip'));
|
|
657
|
+
console.log();
|
|
428
658
|
console.log(chalk.bold('Checklist:'));
|
|
429
659
|
checkbox('Read `.codeyam/editor-mode-context.md` for session state');
|
|
430
660
|
checkbox('Ask the user what they want to build');
|
|
@@ -479,7 +709,15 @@ function printSetup(root) {
|
|
|
479
709
|
console.log();
|
|
480
710
|
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → Mobile, chrome-extension → Custom (400×600).'));
|
|
481
711
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
482
|
-
console.log(chalk.dim(
|
|
712
|
+
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}}'`));
|
|
713
|
+
console.log();
|
|
714
|
+
console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
|
|
715
|
+
console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
|
|
716
|
+
console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
|
|
717
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
718
|
+
console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
|
|
719
|
+
console.log(chalk.dim(' If you discover a new viewport is needed mid-workflow, add it to screenSizes the same way and reference by name.'));
|
|
720
|
+
console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
|
|
483
721
|
console.log();
|
|
484
722
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
485
723
|
console.log();
|
|
@@ -497,6 +735,42 @@ function printSetup(root) {
|
|
|
497
735
|
// ─── Cycle overview (no args, has project) ────────────────────────────
|
|
498
736
|
function printCycleOverview(root, state) {
|
|
499
737
|
logEvent(root, 'overview', state ? { feature: state.feature, step: state.step } : {});
|
|
738
|
+
// Detect active migration (uses both editor-step.json and migration-state.json)
|
|
739
|
+
const migState = readMigrationState(root);
|
|
740
|
+
const resumeInfo = getMigrationResumeInfo(state
|
|
741
|
+
? { step: state.step, label: state.label, migration: state.migration }
|
|
742
|
+
: null, migState);
|
|
743
|
+
if (resumeInfo.mode === 'survey') {
|
|
744
|
+
console.log();
|
|
745
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
746
|
+
console.log();
|
|
747
|
+
console.log(chalk.yellow('Migration survey in progress — Claude needs to explore the project.'));
|
|
748
|
+
console.log();
|
|
749
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
750
|
+
console.log();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (resumeInfo.mode === 'page-in-progress') {
|
|
754
|
+
console.log();
|
|
755
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
756
|
+
console.log();
|
|
757
|
+
console.log(chalk.yellow(`Migration: Page ${resumeInfo.pageIndex + 1}/${resumeInfo.totalPages} (${resumeInfo.pageName})` +
|
|
758
|
+
(resumeInfo.step
|
|
759
|
+
? `, Step ${resumeInfo.step} (${resumeInfo.stepLabel})`
|
|
760
|
+
: '')));
|
|
761
|
+
console.log();
|
|
762
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
763
|
+
console.log();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (resumeInfo.mode === 'complete') {
|
|
767
|
+
console.log();
|
|
768
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
769
|
+
console.log();
|
|
770
|
+
console.log(chalk.green('Migration complete! Start building features:'));
|
|
771
|
+
console.log();
|
|
772
|
+
// Fall through to normal cycle display
|
|
773
|
+
}
|
|
500
774
|
console.log();
|
|
501
775
|
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
502
776
|
console.log();
|
|
@@ -514,7 +788,7 @@ function printCycleOverview(root, state) {
|
|
|
514
788
|
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
515
789
|
}
|
|
516
790
|
else {
|
|
517
|
-
console.log('Each feature follows
|
|
791
|
+
console.log('Each feature follows 16 steps. You MUST run each command in order:');
|
|
518
792
|
console.log();
|
|
519
793
|
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
520
794
|
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
@@ -529,10 +803,14 @@ function printCycleOverview(root, state) {
|
|
|
529
803
|
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
530
804
|
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
531
805
|
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
806
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
807
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
808
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Push')} — Push to remote`);
|
|
532
809
|
console.log();
|
|
533
810
|
console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
|
|
534
811
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
535
812
|
}
|
|
813
|
+
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
536
814
|
console.log();
|
|
537
815
|
}
|
|
538
816
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
@@ -543,19 +821,19 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
543
821
|
if (!isResuming) {
|
|
544
822
|
clearState(root);
|
|
545
823
|
}
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
824
|
+
// Always persist state so step 2's validation sees that step 1 ran.
|
|
825
|
+
// The feature name may not be known yet (it's an output of planning) —
|
|
826
|
+
// use an empty string as placeholder; step 2 requires --feature anyway.
|
|
827
|
+
const now = new Date().toISOString();
|
|
828
|
+
writeState(root, {
|
|
829
|
+
feature: feature || prevState?.feature || '',
|
|
830
|
+
step: 1,
|
|
831
|
+
label: STEP_LABELS[1],
|
|
832
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
833
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
834
|
+
appFormats: options?.appFormats || prevState?.appFormats,
|
|
835
|
+
techStackId: options?.techStackId || prevState?.techStackId,
|
|
836
|
+
});
|
|
559
837
|
// Save the user's original prompt to a separate file
|
|
560
838
|
if (userPrompt) {
|
|
561
839
|
const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
|
|
@@ -580,11 +858,21 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
580
858
|
console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
|
|
581
859
|
console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
|
|
582
860
|
checkbox("Summarize what you'll build in plain language the user can verify against their vision");
|
|
861
|
+
checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
|
|
862
|
+
console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
|
|
583
863
|
checkbox('Set the project title and description for the App tab:');
|
|
584
864
|
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
585
865
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
586
866
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
587
867
|
console.log();
|
|
868
|
+
const designSystem = readDesignSystem(root);
|
|
869
|
+
if (designSystem) {
|
|
870
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
871
|
+
console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
|
|
872
|
+
console.log();
|
|
873
|
+
console.log(designSystem);
|
|
874
|
+
console.log();
|
|
875
|
+
}
|
|
588
876
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
589
877
|
console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
|
|
590
878
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
@@ -612,6 +900,7 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
612
900
|
// ─── Step 2: Prototype ────────────────────────────────────────────────
|
|
613
901
|
function printStep2(root, feature) {
|
|
614
902
|
const port = getServerPort();
|
|
903
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
615
904
|
const projectExists = hasProject(root);
|
|
616
905
|
const prevState = readState(root);
|
|
617
906
|
const isResuming = prevState?.step === 2;
|
|
@@ -649,19 +938,41 @@ function printStep2(root, feature) {
|
|
|
649
938
|
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
650
939
|
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
651
940
|
console.log();
|
|
652
|
-
console.log(chalk.
|
|
941
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
942
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
943
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
944
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
945
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
946
|
+
console.log();
|
|
947
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
653
948
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
654
949
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
655
950
|
console.log();
|
|
656
951
|
console.log(chalk.bold('Build the feature:'));
|
|
657
952
|
}
|
|
953
|
+
else {
|
|
954
|
+
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
955
|
+
checkbox('List existing scenarios: `codeyam editor scenarios`');
|
|
956
|
+
console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
|
|
957
|
+
console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
|
|
958
|
+
checkbox('Pick the scenario with seed data most relevant to this feature');
|
|
959
|
+
console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
|
|
960
|
+
console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
|
|
961
|
+
checkbox('Load that scenario to seed the database and preview it:');
|
|
962
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
963
|
+
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
964
|
+
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
965
|
+
console.log();
|
|
966
|
+
}
|
|
658
967
|
console.log(chalk.bold('Checklist:'));
|
|
659
968
|
checkbox('Create API routes that read from the database via Prisma');
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
969
|
+
if (!projectExists) {
|
|
970
|
+
checkbox('Seed the database with demo data');
|
|
971
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
972
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
973
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
974
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
975
|
+
}
|
|
665
976
|
checkbox('Verify the dev server shows the changes');
|
|
666
977
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
667
978
|
// Responsive design guidance when building a mobile-responsive web app
|
|
@@ -673,14 +984,33 @@ function printStep2(root, feature) {
|
|
|
673
984
|
console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
|
|
674
985
|
console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
|
|
675
986
|
}
|
|
987
|
+
const designSystem = readDesignSystem(root);
|
|
988
|
+
if (designSystem) {
|
|
989
|
+
console.log();
|
|
990
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
991
|
+
console.log();
|
|
992
|
+
console.log(designSystem);
|
|
993
|
+
console.log();
|
|
994
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
995
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
996
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
997
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
998
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
999
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1000
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1001
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1002
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1003
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1004
|
+
}
|
|
676
1005
|
console.log();
|
|
677
1006
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
678
1007
|
console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
|
|
679
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
1008
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
680
1009
|
console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
|
|
681
1010
|
console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
|
|
682
1011
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
683
|
-
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1"}'`));
|
|
1012
|
+
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1013
|
+
printDimensionGuidance(dim, dimNames);
|
|
684
1014
|
console.log();
|
|
685
1015
|
console.log(chalk.bold('Verify the dev server:'));
|
|
686
1016
|
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
@@ -701,12 +1031,19 @@ function printStep2(root, feature) {
|
|
|
701
1031
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
702
1032
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
703
1033
|
console.log();
|
|
1034
|
+
console.log(chalk.bold('Update README and setup script:'));
|
|
1035
|
+
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1036
|
+
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
1037
|
+
console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
|
|
1038
|
+
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1039
|
+
console.log();
|
|
704
1040
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
705
1041
|
stopGate(2);
|
|
706
1042
|
}
|
|
707
1043
|
// ─── Step 3: Confirm ──────────────────────────────────────────────────
|
|
708
1044
|
function printStep3(root, feature) {
|
|
709
1045
|
const port = getServerPort();
|
|
1046
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
710
1047
|
const prevState = readState(root);
|
|
711
1048
|
const isResuming = prevState?.step === 3;
|
|
712
1049
|
const now = new Date().toISOString();
|
|
@@ -725,7 +1062,7 @@ function printStep3(root, feature) {
|
|
|
725
1062
|
console.log('Summarize what was built and get user confirmation.');
|
|
726
1063
|
console.log();
|
|
727
1064
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
728
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
|
|
1065
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
729
1066
|
checkbox('Verify API routes return valid data (curl each route)');
|
|
730
1067
|
console.log();
|
|
731
1068
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
@@ -739,7 +1076,8 @@ function printStep3(root, feature) {
|
|
|
739
1076
|
console.log();
|
|
740
1077
|
console.log(chalk.bold('Then present to the user:'));
|
|
741
1078
|
checkbox('Summarize what was built (routes, components, data)');
|
|
742
|
-
checkbox(
|
|
1079
|
+
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
1080
|
+
printDimensionGuidance(dim, dimNames);
|
|
743
1081
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
744
1082
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
745
1083
|
console.log();
|
|
@@ -751,7 +1089,7 @@ function printStep3(root, feature) {
|
|
|
751
1089
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
752
1090
|
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 4'));
|
|
753
1091
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
754
|
-
chalk.dim(' —
|
|
1092
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 3`'));
|
|
755
1093
|
console.log();
|
|
756
1094
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
757
1095
|
stopGate(3, { confirm: true });
|
|
@@ -776,39 +1114,13 @@ function printStep4(root, feature) {
|
|
|
776
1114
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
777
1115
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
|
|
778
1116
|
console.log();
|
|
779
|
-
|
|
780
|
-
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
781
|
-
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
782
|
-
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
783
|
-
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
784
|
-
console.log();
|
|
785
|
-
console.log(chalk.bold('Checklist:'));
|
|
786
|
-
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
787
|
-
checkbox('Read EVERY file created or modified in this session');
|
|
788
|
-
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
789
|
-
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
790
|
-
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
791
|
-
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
792
|
-
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
793
|
-
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
794
|
-
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
795
|
-
console.log();
|
|
796
|
-
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
797
|
-
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
798
|
-
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
799
|
-
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
800
|
-
console.log();
|
|
801
|
-
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
802
|
-
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
803
|
-
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
804
|
-
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
805
|
-
console.log();
|
|
806
|
-
console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
|
|
1117
|
+
printExtractionPlanInstructions();
|
|
807
1118
|
stopGate(4);
|
|
808
1119
|
}
|
|
809
1120
|
// ─── Step 5: Extract ──────────────────────────────────────────────────
|
|
810
1121
|
function printStep5(root, feature) {
|
|
811
1122
|
const port = getServerPort();
|
|
1123
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
812
1124
|
const prevState = readState(root);
|
|
813
1125
|
const isResuming = prevState?.step === 5;
|
|
814
1126
|
const now = new Date().toISOString();
|
|
@@ -832,12 +1144,13 @@ function printStep5(root, feature) {
|
|
|
832
1144
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
833
1145
|
console.log(chalk.dim(' No tests needed — visual verification happens in step 7'));
|
|
834
1146
|
console.log();
|
|
835
|
-
console.log(chalk.bold('Library functions (TDD):'));
|
|
836
|
-
checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1147
|
+
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
1148
|
+
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
837
1149
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
838
1150
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1151
|
+
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
839
1152
|
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
840
|
-
console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step 7 only captures component screenshots.'));
|
|
1153
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 7 only captures component screenshots.'));
|
|
841
1154
|
console.log();
|
|
842
1155
|
console.log(chalk.bold('Recursive pass:'));
|
|
843
1156
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
@@ -849,7 +1162,8 @@ function printStep5(root, feature) {
|
|
|
849
1162
|
checkbox('Run all tests and verify they pass');
|
|
850
1163
|
checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
|
|
851
1164
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
852
|
-
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
|
|
1165
|
+
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1166
|
+
printDimensionGuidance(dim, dimNames);
|
|
853
1167
|
console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
|
|
854
1168
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
855
1169
|
console.log();
|
|
@@ -875,19 +1189,7 @@ function printStep6(root, feature) {
|
|
|
875
1189
|
}
|
|
876
1190
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
877
1191
|
console.log();
|
|
878
|
-
|
|
879
|
-
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
880
|
-
checkbox('Add an entry for each new function/component extracted in step 5');
|
|
881
|
-
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
882
|
-
checkbox('For each function with a test file from step 5, set `testFile` to the relative path');
|
|
883
|
-
console.log();
|
|
884
|
-
console.log(chalk.bold('Entry format:'));
|
|
885
|
-
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
886
|
-
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
887
|
-
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
888
|
-
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
889
|
-
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
890
|
-
console.log(chalk.dim(` "feature": "${feature}", "createdAt": "..." }`));
|
|
1192
|
+
printGlossaryInstructions(feature);
|
|
891
1193
|
console.log();
|
|
892
1194
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
893
1195
|
stopGate(6);
|
|
@@ -895,6 +1197,7 @@ function printStep6(root, feature) {
|
|
|
895
1197
|
// ─── Step 7: Analyze ──────────────────────────────────────────────────
|
|
896
1198
|
function printStep7(root, feature) {
|
|
897
1199
|
const port = getServerPort();
|
|
1200
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
898
1201
|
const prevState = readState(root);
|
|
899
1202
|
const isResuming = prevState?.step === 7;
|
|
900
1203
|
const now = new Date().toISOString();
|
|
@@ -914,48 +1217,11 @@ function printStep7(root, feature) {
|
|
|
914
1217
|
console.log();
|
|
915
1218
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
916
1219
|
checkbox('List all files with new/modified visual components from step 5');
|
|
917
|
-
checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
|
|
918
|
-
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
919
|
-
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
920
1220
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
921
1221
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
922
1222
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
923
1223
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
924
|
-
|
|
925
|
-
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
926
|
-
console.log(chalk.dim(' — Props/interface'));
|
|
927
|
-
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
928
|
-
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
929
|
-
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
930
|
-
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
931
|
-
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
932
|
-
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
933
|
-
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
934
|
-
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
935
|
-
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
936
|
-
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
937
|
-
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
938
|
-
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
939
|
-
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
|
|
940
|
-
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
941
|
-
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
942
|
-
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
943
|
-
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
944
|
-
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
945
|
-
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
946
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
947
|
-
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
948
|
-
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
949
|
-
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
950
|
-
console.log(chalk.dim(' Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size'));
|
|
951
|
-
console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
|
|
952
|
-
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
953
|
-
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
954
|
-
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
955
|
-
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
956
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
957
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
958
|
-
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
1224
|
+
printComponentCaptureInstructions();
|
|
959
1225
|
console.log();
|
|
960
1226
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
961
1227
|
checkbox('Run ALL test files created in step 5');
|
|
@@ -965,17 +1231,16 @@ function printStep7(root, feature) {
|
|
|
965
1231
|
console.log();
|
|
966
1232
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
967
1233
|
console.log();
|
|
968
|
-
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
|
|
1234
|
+
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1235
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 8 will refuse to run until the audit passes.'));
|
|
1236
|
+
console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
|
|
969
1237
|
console.log();
|
|
970
|
-
console.log(chalk.bold('Build import graph (for change tracking):'));
|
|
971
|
-
checkbox('Run `codeyam editor analyze-imports`');
|
|
972
|
-
console.log(chalk.dim(' This populates the import graph so the system can detect impacted entities when code changes.'));
|
|
973
|
-
console.log(chalk.dim(' It must run AFTER the audit passes so the glossary and entity data are complete.'));
|
|
974
1238
|
stopGate(7);
|
|
975
1239
|
}
|
|
976
1240
|
// ─── Step 8: App Scenarios ────────────────────────────────────────────
|
|
977
1241
|
function printStep8(root, feature) {
|
|
978
1242
|
const port = getServerPort();
|
|
1243
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
979
1244
|
const prevState = readState(root);
|
|
980
1245
|
const isResuming = prevState?.step === 8;
|
|
981
1246
|
const now = new Date().toISOString();
|
|
@@ -991,43 +1256,43 @@ function printStep8(root, feature) {
|
|
|
991
1256
|
if (isResuming) {
|
|
992
1257
|
printResumptionHeader(8);
|
|
993
1258
|
}
|
|
994
|
-
console.log('Create app-level scenarios
|
|
1259
|
+
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1260
|
+
console.log();
|
|
1261
|
+
console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
|
|
1262
|
+
console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
|
|
1263
|
+
console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
|
|
1264
|
+
console.log();
|
|
1265
|
+
console.log(chalk.bold.cyan('Make seed data work hard:'));
|
|
1266
|
+
console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
|
|
1267
|
+
console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
|
|
1268
|
+
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1269
|
+
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1270
|
+
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1271
|
+
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1272
|
+
console.log();
|
|
1273
|
+
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
1274
|
+
console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
|
|
1275
|
+
console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
|
|
1276
|
+
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1277
|
+
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
995
1278
|
console.log();
|
|
996
|
-
console.log(chalk.bold('Checklist:'));
|
|
997
|
-
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
998
|
-
console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
|
|
999
|
-
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
1000
1279
|
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1001
1280
|
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1002
1281
|
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1003
1282
|
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1004
|
-
|
|
1005
|
-
console.log(chalk.yellow(' Each page needs its own scenarios — a detail page needs different data than the catalog'));
|
|
1006
|
-
console.log(chalk.dim(' Catalog: "Full Catalog", "Empty Catalog", "Single Category"'));
|
|
1007
|
-
console.log(chalk.dim(' Detail: "Detail - With Reviews", "Detail - No Reviews", "Detail - High Rated"'));
|
|
1008
|
-
console.log(chalk.dim(' Each scenario provides seed data to populate the database for that state'));
|
|
1009
|
-
checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
|
|
1010
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","seed":{"products":[...],"categories":[...]}}'`));
|
|
1011
|
-
console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
|
|
1012
|
-
checkbox('Optional: add "viewportWidth" and "viewportHeight" to override the project default screen size');
|
|
1013
|
-
console.log(chalk.dim(' If omitted, captures use the project default from setup. Only override for scenarios that need a different size.'));
|
|
1014
|
-
checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
|
|
1015
|
-
console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
|
|
1016
|
-
console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
|
|
1017
|
-
console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
|
|
1018
|
-
checkbox('For large seed data, write JSON to a project temp file and use @ prefix:');
|
|
1019
|
-
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
1020
|
-
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
1021
|
-
checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
|
|
1022
|
-
console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
|
|
1023
|
-
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
1024
|
-
checkbox('If no database, use component-style mock scenarios (unchanged):');
|
|
1025
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1026
|
-
checkbox('After each registration, check the response for `clientErrors`');
|
|
1027
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1028
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1283
|
+
printAppScenarioInstructions();
|
|
1029
1284
|
console.log();
|
|
1030
1285
|
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
|
|
1286
|
+
console.log();
|
|
1287
|
+
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1288
|
+
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
1289
|
+
console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
|
|
1290
|
+
console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
|
|
1291
|
+
console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
|
|
1292
|
+
console.log();
|
|
1293
|
+
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1294
|
+
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1295
|
+
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1031
1296
|
stopGate(8);
|
|
1032
1297
|
}
|
|
1033
1298
|
// ─── Step 9: User Scenarios ───────────────────────────────────────────
|
|
@@ -1048,34 +1313,37 @@ function printStep9(root, feature) {
|
|
|
1048
1313
|
if (isResuming) {
|
|
1049
1314
|
printResumptionHeader(9);
|
|
1050
1315
|
}
|
|
1051
|
-
console.log('Create per-persona
|
|
1316
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 10 if no users.');
|
|
1052
1317
|
console.log();
|
|
1053
|
-
console.log(chalk.bold('If the app has users:'));
|
|
1054
|
-
|
|
1055
|
-
console.log(
|
|
1056
|
-
console.log(chalk.
|
|
1057
|
-
|
|
1318
|
+
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1319
|
+
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
1320
|
+
console.log();
|
|
1321
|
+
console.log(chalk.bold('If the app has users/auth:'));
|
|
1322
|
+
console.log();
|
|
1323
|
+
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1324
|
+
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1325
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 8, with user-specific state layered on.'));
|
|
1326
|
+
console.log();
|
|
1327
|
+
console.log(chalk.bold('Checklist:'));
|
|
1328
|
+
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1329
|
+
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1330
|
+
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1331
|
+
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1332
|
+
console.log(chalk.dim(' Step 8 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1333
|
+
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1058
1334
|
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1059
|
-
checkbox('
|
|
1060
|
-
console.log(chalk.dim(
|
|
1061
|
-
console.log(chalk.dim(' Always include "url" — the page to screenshot (e.g. "/", "/dashboard", "/drinks/1")'));
|
|
1062
|
-
console.log(chalk.dim(" The base scenario's seed data is merged with this scenario's seed data (overlay wins)."));
|
|
1063
|
-
checkbox('If the app uses auth or other patterns: see FEATURE_PATTERNS.md for per-persona scenario guidance');
|
|
1064
|
-
checkbox('If using mock-based scenarios, register with mockData as before:');
|
|
1065
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"...","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1335
|
+
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
1336
|
+
console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
|
|
1066
1337
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
1067
1338
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1068
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1069
1339
|
console.log();
|
|
1070
|
-
console.log(chalk.
|
|
1071
|
-
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
1072
|
-
console.log();
|
|
1073
|
-
console.log(chalk.dim('Focus on creating user-persona scenarios (or skip if no users). Code fixes happen in step 10 if needed.'));
|
|
1340
|
+
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1074
1341
|
stopGate(9);
|
|
1075
1342
|
}
|
|
1076
1343
|
// ─── Step 10: Verify ──────────────────────────────────────────────────
|
|
1077
1344
|
function printStep10(root, feature) {
|
|
1078
1345
|
const port = getServerPort();
|
|
1346
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1079
1347
|
const prevState = readState(root);
|
|
1080
1348
|
const isResuming = prevState?.step === 10;
|
|
1081
1349
|
const now = new Date().toISOString();
|
|
@@ -1100,7 +1368,8 @@ function printStep10(root, feature) {
|
|
|
1100
1368
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
|
|
1101
1369
|
console.log();
|
|
1102
1370
|
console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
|
|
1103
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1371
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1372
|
+
printDimensionGuidance(dim, dimNames);
|
|
1104
1373
|
checkbox('Click through each app-level and user-persona scenario in the preview');
|
|
1105
1374
|
checkbox('For seed-based scenarios: verify data renders correctly after switching');
|
|
1106
1375
|
console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
|
|
@@ -1153,6 +1422,7 @@ function printStep11(root, feature) {
|
|
|
1153
1422
|
// ─── Step 12: Review ──────────────────────────────────────────────────
|
|
1154
1423
|
function printStep12(root, feature) {
|
|
1155
1424
|
const port = getServerPort();
|
|
1425
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1156
1426
|
const prevState = readState(root);
|
|
1157
1427
|
const isResuming = prevState?.step === 12;
|
|
1158
1428
|
const now = new Date().toISOString();
|
|
@@ -1171,20 +1441,21 @@ function printStep12(root, feature) {
|
|
|
1171
1441
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1172
1442
|
console.log();
|
|
1173
1443
|
console.log(chalk.bold('Checklist (do all of this silently):'));
|
|
1174
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1444
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1445
|
+
printDimensionGuidance(dim, dimNames);
|
|
1175
1446
|
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
1176
1447
|
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
1177
1448
|
checkbox(`Check for client errors: \`codeyam editor client-errors\``);
|
|
1178
1449
|
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1179
1450
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1180
1451
|
checkbox('Fix or remove any broken images before continuing');
|
|
1452
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1181
1453
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1182
1454
|
checkbox('Do not proceed until all checks pass');
|
|
1183
1455
|
stopGate(12);
|
|
1184
1456
|
}
|
|
1185
1457
|
// ─── Step 13: Present ─────────────────────────────────────────────────
|
|
1186
1458
|
function printStep13(root, feature) {
|
|
1187
|
-
const port = getServerPort();
|
|
1188
1459
|
const prevState = readState(root);
|
|
1189
1460
|
const isResuming = prevState?.step === 13;
|
|
1190
1461
|
const now = new Date().toISOString();
|
|
@@ -1203,16 +1474,9 @@ function printStep13(root, feature) {
|
|
|
1203
1474
|
console.log('Present the results to the user and get their approval.');
|
|
1204
1475
|
console.log();
|
|
1205
1476
|
console.log(chalk.bold('Checklist:'));
|
|
1206
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1207
|
-
console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
|
|
1208
|
-
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1209
|
-
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1210
|
-
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1211
|
-
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1212
|
-
checkbox('Switch the preview to that scenario using its `id`:');
|
|
1213
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>"}'`));
|
|
1214
1477
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1215
1478
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1479
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1216
1480
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1217
1481
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1218
1482
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1224,17 +1488,7 @@ function printStep13(root, feature) {
|
|
|
1224
1488
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1225
1489
|
console.log();
|
|
1226
1490
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1227
|
-
checkbox(
|
|
1228
|
-
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1229
|
-
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1230
|
-
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1231
|
-
console.log(chalk.green(' Then run: ') +
|
|
1232
|
-
chalk.bold('codeyam editor steps') +
|
|
1233
|
-
chalk.green(' to start the next feature'));
|
|
1234
|
-
console.log();
|
|
1235
|
-
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
1236
|
-
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
1237
|
-
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
1491
|
+
checkbox('Advance to the commit step: `codeyam editor 14`');
|
|
1238
1492
|
console.log();
|
|
1239
1493
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1240
1494
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1244,45 +1498,775 @@ function printStep13(root, feature) {
|
|
|
1244
1498
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1245
1499
|
stopGate(13, { confirm: true });
|
|
1246
1500
|
}
|
|
1247
|
-
// ───
|
|
1248
|
-
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
1501
|
+
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1249
1502
|
/**
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
* Runs data-structure-only analysis for all glossary entities, then outputs
|
|
1253
|
-
* an import graph and entity data structures as JSON to stdout.
|
|
1503
|
+
* Print a progress tracker for the 8 migration steps.
|
|
1254
1504
|
*/
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1505
|
+
function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
|
|
1506
|
+
console.log();
|
|
1507
|
+
console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
|
|
1508
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
1509
|
+
for (let i = 1; i <= 10; i++) {
|
|
1510
|
+
const label = MIGRATION_STEP_LABELS[i];
|
|
1511
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
1512
|
+
const content = `${num}. ${label.padEnd(28)}`;
|
|
1513
|
+
if (i < current) {
|
|
1514
|
+
console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
|
|
1515
|
+
}
|
|
1516
|
+
else if (i === current) {
|
|
1517
|
+
console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
console.log(chalk.dim(` │ ○ ${content}│`));
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
console.log(chalk.dim(' └─────────────────────────────────────┘'));
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Print a STOP gate for migration steps.
|
|
1527
|
+
*/
|
|
1528
|
+
function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
|
|
1529
|
+
console.log();
|
|
1530
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
1531
|
+
console.log();
|
|
1532
|
+
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
1533
|
+
if (opts?.confirm) {
|
|
1534
|
+
console.log();
|
|
1535
|
+
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
1536
|
+
}
|
|
1537
|
+
console.log();
|
|
1538
|
+
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
1539
|
+
printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
|
|
1540
|
+
console.log();
|
|
1541
|
+
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
1542
|
+
console.log();
|
|
1543
|
+
if (current === 5) {
|
|
1544
|
+
// Step 5 (Discuss) can skip to step 9 if user declines decomposition
|
|
1545
|
+
console.log(chalk.green('If decomposing, run: ') +
|
|
1546
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1547
|
+
console.log(chalk.green('If skipping decomposition, run: ') +
|
|
1548
|
+
chalk.bold('codeyam editor migrate 9'));
|
|
1549
|
+
}
|
|
1550
|
+
else if (current < 10) {
|
|
1551
|
+
console.log(chalk.green('When done, run: ') +
|
|
1552
|
+
chalk.bold(`codeyam editor migrate ${current + 1}`));
|
|
1553
|
+
}
|
|
1554
|
+
else {
|
|
1555
|
+
console.log(chalk.green('Page complete! Run: ') +
|
|
1556
|
+
chalk.bold('codeyam editor migrate next') +
|
|
1557
|
+
chalk.green(' to start the next page'));
|
|
1558
|
+
}
|
|
1559
|
+
console.log();
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Write migration-aware editor state.
|
|
1563
|
+
*/
|
|
1564
|
+
function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
|
|
1565
|
+
const prevState = readState(root);
|
|
1566
|
+
const isResuming = prevState?.step === step && !!prevState?.migration;
|
|
1567
|
+
const now = new Date().toISOString();
|
|
1568
|
+
writeState(root, {
|
|
1569
|
+
feature: `Migration: ${pageName}`,
|
|
1570
|
+
step,
|
|
1571
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1572
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1573
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1574
|
+
migration: {
|
|
1575
|
+
pageName,
|
|
1576
|
+
pageIndex,
|
|
1577
|
+
totalPages,
|
|
1578
|
+
},
|
|
1579
|
+
});
|
|
1580
|
+
logEvent(root, 'step', {
|
|
1581
|
+
step,
|
|
1582
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1583
|
+
feature: `Migration: ${pageName}`,
|
|
1584
|
+
migration: true,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Get the current migration page info from state, or return null.
|
|
1589
|
+
*/
|
|
1590
|
+
function getCurrentMigrationPage(root) {
|
|
1591
|
+
const migState = readMigrationState(root);
|
|
1592
|
+
if (!migState || migState.pages.length === 0)
|
|
1593
|
+
return null;
|
|
1594
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
1595
|
+
if (!page)
|
|
1596
|
+
return null;
|
|
1597
|
+
return {
|
|
1598
|
+
pageName: page.name,
|
|
1599
|
+
pageIndex: migState.currentPageIndex,
|
|
1600
|
+
totalPages: migState.pages.length,
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
// ─── Migration Step 1: Survey ───────────────────────────────────────
|
|
1604
|
+
function printMigrateStep1(root) {
|
|
1605
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1606
|
+
if (!pageInfo) {
|
|
1607
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1608
|
+
process.exit(1);
|
|
1609
|
+
}
|
|
1610
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1611
|
+
writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
|
|
1612
|
+
const migState = readMigrationState(root);
|
|
1613
|
+
const page = migState.pages[pageIndex];
|
|
1614
|
+
console.log();
|
|
1615
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1616
|
+
console.log();
|
|
1617
|
+
console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
|
|
1618
|
+
console.log();
|
|
1619
|
+
console.log(chalk.bold('Survey:'));
|
|
1620
|
+
checkbox(`Read the page file: \`${page.filePath}\``);
|
|
1621
|
+
checkbox('Follow every import — read all local components, hooks, utilities used by this page');
|
|
1622
|
+
checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
|
|
1623
|
+
checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
|
|
1624
|
+
checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
|
|
1625
|
+
checkbox('Note the page route and any dynamic segments');
|
|
1626
|
+
console.log();
|
|
1627
|
+
console.log(chalk.bold('Dev Server:'));
|
|
1628
|
+
checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
|
|
1629
|
+
checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
|
|
1630
|
+
console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
|
|
1631
|
+
console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
|
|
1632
|
+
checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
|
|
1633
|
+
checkbox("Verify it's running: check for successful startup output");
|
|
1634
|
+
console.log();
|
|
1635
|
+
migrationStopGate(1, pageName, pageIndex, totalPages);
|
|
1636
|
+
}
|
|
1637
|
+
// ─── Migration Step 2: App Scenarios ────────────────────────────────
|
|
1638
|
+
function printMigrateStep2(root) {
|
|
1639
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1640
|
+
if (!pageInfo) {
|
|
1641
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1642
|
+
process.exit(1);
|
|
1643
|
+
}
|
|
1644
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1645
|
+
writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
|
|
1646
|
+
const migState = readMigrationState(root);
|
|
1647
|
+
const page = migState.pages[pageIndex];
|
|
1648
|
+
console.log();
|
|
1649
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1650
|
+
console.log();
|
|
1651
|
+
console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
|
|
1652
|
+
console.log();
|
|
1653
|
+
console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
|
|
1654
|
+
console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
|
|
1655
|
+
console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
|
|
1656
|
+
console.log();
|
|
1657
|
+
console.log(chalk.bold('Seed Adapter Setup:'));
|
|
1658
|
+
checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
|
|
1659
|
+
console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
|
|
1660
|
+
console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
|
|
1661
|
+
console.log();
|
|
1662
|
+
console.log(chalk.dim(' How to create a seed adapter:'));
|
|
1663
|
+
console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
|
|
1664
|
+
console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
|
|
1665
|
+
console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
|
|
1666
|
+
console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
|
|
1667
|
+
console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
|
|
1668
|
+
console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
|
|
1669
|
+
console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
|
|
1670
|
+
console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
|
|
1671
|
+
console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
|
|
1672
|
+
console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
|
|
1673
|
+
console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
|
|
1674
|
+
console.log();
|
|
1675
|
+
printAppScenarioInstructions(pageName, page.route);
|
|
1676
|
+
console.log();
|
|
1677
|
+
migrationStopGate(2, pageName, pageIndex, totalPages);
|
|
1678
|
+
}
|
|
1679
|
+
// ─── Migration Step 3: Component Scenarios ──────────────────────────
|
|
1680
|
+
function printMigrateStep3(root) {
|
|
1681
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1682
|
+
if (!pageInfo) {
|
|
1683
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1684
|
+
process.exit(1);
|
|
1685
|
+
}
|
|
1686
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1687
|
+
writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
|
|
1688
|
+
console.log();
|
|
1689
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1690
|
+
console.log();
|
|
1691
|
+
console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
|
|
1692
|
+
console.log();
|
|
1693
|
+
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1694
|
+
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1695
|
+
console.log();
|
|
1696
|
+
printComponentCaptureInstructions();
|
|
1697
|
+
console.log();
|
|
1698
|
+
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1699
|
+
}
|
|
1700
|
+
// ─── Migration Step 4: Preview ──────────────────────────────────────
|
|
1701
|
+
function printMigrateStep4(root) {
|
|
1702
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1703
|
+
if (!pageInfo) {
|
|
1704
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1705
|
+
process.exit(1);
|
|
1706
|
+
}
|
|
1707
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1708
|
+
writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
|
|
1709
|
+
console.log();
|
|
1710
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1711
|
+
console.log();
|
|
1712
|
+
console.log(chalk.bold('Goal: Verify screenshots look correct and show results to the user.'));
|
|
1713
|
+
console.log();
|
|
1714
|
+
console.log(chalk.bold('Checklist:'));
|
|
1715
|
+
checkbox('Review all scenario screenshots for this page — do they look correct?');
|
|
1716
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1717
|
+
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1718
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1719
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1720
|
+
console.log();
|
|
1721
|
+
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1722
|
+
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
1723
|
+
}
|
|
1724
|
+
// ─── Migration Step 5: Discuss ──────────────────────────────────────
|
|
1725
|
+
function printMigrateStep5(root) {
|
|
1726
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1727
|
+
if (!pageInfo) {
|
|
1728
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1729
|
+
process.exit(1);
|
|
1730
|
+
}
|
|
1731
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1732
|
+
writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
|
|
1733
|
+
console.log();
|
|
1734
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1735
|
+
console.log();
|
|
1736
|
+
console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
|
|
1737
|
+
console.log();
|
|
1738
|
+
console.log(chalk.bold('Checklist:'));
|
|
1739
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1740
|
+
checkbox("Assess the page's complexity:");
|
|
1741
|
+
console.log(chalk.dim(' — How many inline components exist?'));
|
|
1742
|
+
console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
|
|
1743
|
+
console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
|
|
1744
|
+
console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
|
|
1745
|
+
checkbox('Present your assessment to the user');
|
|
1746
|
+
console.log();
|
|
1747
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1748
|
+
console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
|
|
1749
|
+
chalk.dim(' — continue to step 6 (Decompose)'));
|
|
1750
|
+
console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
|
|
1751
|
+
chalk.dim(' — skip to step 9 (Journal)'));
|
|
1752
|
+
console.log();
|
|
1753
|
+
console.log(chalk.bold.yellow('Routing:'));
|
|
1754
|
+
console.log(chalk.yellow(' If user chooses decomposition: ') +
|
|
1755
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1756
|
+
console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
|
|
1757
|
+
migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
|
|
1758
|
+
}
|
|
1759
|
+
// ─── Migration Step 6: Decompose ────────────────────────────────────
|
|
1760
|
+
function printMigrateStep6(root) {
|
|
1761
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1762
|
+
if (!pageInfo) {
|
|
1763
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1764
|
+
process.exit(1);
|
|
1765
|
+
}
|
|
1766
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1767
|
+
writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
|
|
1768
|
+
console.log();
|
|
1769
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1770
|
+
console.log();
|
|
1771
|
+
console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
|
|
1772
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1773
|
+
console.log();
|
|
1774
|
+
console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
|
|
1775
|
+
console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
|
|
1776
|
+
console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
|
|
1777
|
+
console.log();
|
|
1778
|
+
printExtractionPlanInstructions();
|
|
1779
|
+
migrationStopGate(6, pageName, pageIndex, totalPages);
|
|
1780
|
+
}
|
|
1781
|
+
// ─── Migration Step 7: Extract ──────────────────────────────────────
|
|
1782
|
+
function printMigrateStep7(root) {
|
|
1783
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1784
|
+
if (!pageInfo) {
|
|
1785
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1786
|
+
process.exit(1);
|
|
1787
|
+
}
|
|
1788
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1789
|
+
writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
|
|
1790
|
+
console.log();
|
|
1791
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1792
|
+
console.log();
|
|
1793
|
+
console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
|
|
1794
|
+
console.log();
|
|
1795
|
+
console.log(chalk.bold('Checklist:'));
|
|
1796
|
+
checkbox('Extract visual components (no tests needed for components)');
|
|
1797
|
+
checkbox('Extract library functions via TDD (write failing test first, then implement)');
|
|
1798
|
+
checkbox('Extract hooks as needed');
|
|
1799
|
+
checkbox('Recursive pass: check each extracted component for further extraction');
|
|
1800
|
+
console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
|
|
1801
|
+
checkbox('Verify the page file is ONLY imports + component composition');
|
|
1802
|
+
checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1803
|
+
console.log();
|
|
1804
|
+
console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
|
|
1805
|
+
migrationStopGate(7, pageName, pageIndex, totalPages);
|
|
1806
|
+
}
|
|
1807
|
+
// ─── Migration Step 8: Recapture ────────────────────────────────────
|
|
1808
|
+
function printMigrateStep8(root) {
|
|
1809
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1810
|
+
if (!pageInfo) {
|
|
1811
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1812
|
+
process.exit(1);
|
|
1813
|
+
}
|
|
1814
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1815
|
+
writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
|
|
1816
|
+
const migState = readMigrationState(root);
|
|
1817
|
+
const page = migState.pages[pageIndex];
|
|
1818
|
+
console.log();
|
|
1819
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1820
|
+
console.log();
|
|
1821
|
+
console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
|
|
1822
|
+
console.log();
|
|
1823
|
+
console.log(chalk.bold('Glossary:'));
|
|
1824
|
+
printGlossaryInstructions();
|
|
1825
|
+
console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
|
|
1826
|
+
checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
|
|
1827
|
+
console.log();
|
|
1828
|
+
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1829
|
+
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1830
|
+
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1831
|
+
checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
|
|
1832
|
+
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1833
|
+
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1834
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
1835
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1836
|
+
console.log();
|
|
1837
|
+
console.log(chalk.bold('Component Scenarios:'));
|
|
1838
|
+
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1839
|
+
printComponentCaptureInstructions();
|
|
1840
|
+
console.log();
|
|
1841
|
+
console.log(chalk.bold('Verify:'));
|
|
1842
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
1843
|
+
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1844
|
+
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1845
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1846
|
+
checkbox('Fix any issues before proceeding');
|
|
1847
|
+
console.log();
|
|
1848
|
+
migrationStopGate(8, pageName, pageIndex, totalPages);
|
|
1849
|
+
}
|
|
1850
|
+
// ─── Migration Step 9: Journal ──────────────────────────────────────
|
|
1851
|
+
function printMigrateStep9(root) {
|
|
1852
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1853
|
+
if (!pageInfo) {
|
|
1854
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1855
|
+
process.exit(1);
|
|
1856
|
+
}
|
|
1857
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1858
|
+
writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
|
|
1859
|
+
console.log();
|
|
1860
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1861
|
+
console.log();
|
|
1862
|
+
console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
|
|
1863
|
+
console.log();
|
|
1864
|
+
console.log(chalk.bold('Checklist:'));
|
|
1865
|
+
checkbox('Write a concise description of what was migrated (2-3 sentences)');
|
|
1866
|
+
checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
|
|
1867
|
+
console.log();
|
|
1868
|
+
migrationStopGate(9, pageName, pageIndex, totalPages);
|
|
1869
|
+
}
|
|
1870
|
+
// ─── Migration Step 10: Present ─────────────────────────────────────
|
|
1871
|
+
function printMigrateStep10(root) {
|
|
1872
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1873
|
+
if (!pageInfo) {
|
|
1874
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1875
|
+
process.exit(1);
|
|
1876
|
+
}
|
|
1877
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1878
|
+
writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
|
|
1879
|
+
console.log();
|
|
1880
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1881
|
+
console.log();
|
|
1882
|
+
console.log("Show results and commit this page's migration.");
|
|
1883
|
+
console.log();
|
|
1884
|
+
console.log(chalk.bold('Checklist:'));
|
|
1885
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1886
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1887
|
+
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1888
|
+
console.log();
|
|
1889
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1890
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1891
|
+
chalk.dim(" — commit this page's migration"));
|
|
1892
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1893
|
+
chalk.dim(' — make changes, then re-verify'));
|
|
1894
|
+
console.log();
|
|
1895
|
+
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1896
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1897
|
+
checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
|
|
1898
|
+
checkbox('Mark page complete: `codeyam editor migrate complete`');
|
|
1899
|
+
const remaining = pageInfo.totalPages - pageIndex - 1;
|
|
1900
|
+
if (remaining > 0) {
|
|
1901
|
+
checkbox('Advance to next page: `codeyam editor migrate next`');
|
|
1902
|
+
console.log();
|
|
1903
|
+
console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
|
|
1904
|
+
}
|
|
1905
|
+
else {
|
|
1906
|
+
console.log();
|
|
1907
|
+
console.log(chalk.bold.green('Migration complete! All pages processed.'));
|
|
1908
|
+
console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
|
|
1909
|
+
console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
|
|
1910
|
+
}
|
|
1911
|
+
console.log();
|
|
1912
|
+
console.log(chalk.bold('If the user chooses "Make changes":'));
|
|
1913
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1914
|
+
checkbox('Ask what changes the user wants');
|
|
1915
|
+
checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
|
|
1916
|
+
console.log();
|
|
1917
|
+
migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
|
|
1918
|
+
}
|
|
1919
|
+
// ─── Migration Survey ───────────────────────────────────────────────
|
|
1920
|
+
function printMigrateSurvey(root) {
|
|
1921
|
+
console.log();
|
|
1922
|
+
console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
|
|
1923
|
+
console.log();
|
|
1924
|
+
console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
|
|
1925
|
+
console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
|
|
1926
|
+
console.log();
|
|
1927
|
+
// Write editor state so hooks and printCycleOverview detect migration
|
|
1928
|
+
const now = new Date().toISOString();
|
|
1929
|
+
writeState(root, {
|
|
1930
|
+
feature: 'Migration: Survey',
|
|
1931
|
+
step: 0,
|
|
1932
|
+
label: 'Survey',
|
|
1933
|
+
startedAt: now,
|
|
1934
|
+
featureStartedAt: now,
|
|
1935
|
+
migration: {
|
|
1936
|
+
pageName: 'Survey',
|
|
1937
|
+
pageIndex: -1,
|
|
1938
|
+
totalPages: 0,
|
|
1939
|
+
},
|
|
1940
|
+
});
|
|
1941
|
+
logEvent(root, 'migration-survey', {});
|
|
1942
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
1943
|
+
console.log();
|
|
1944
|
+
console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
|
|
1945
|
+
console.log(chalk.green('After confirmation and writing migration-state.json: ') +
|
|
1946
|
+
chalk.bold('codeyam editor migrate 1'));
|
|
1947
|
+
console.log();
|
|
1948
|
+
}
|
|
1949
|
+
// ─── Migration Status ───────────────────────────────────────────────
|
|
1950
|
+
function printMigrationStatus(root) {
|
|
1951
|
+
const state = readMigrationState(root);
|
|
1952
|
+
if (!state) {
|
|
1953
|
+
console.log(chalk.dim('No migration in progress.'));
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
console.log();
|
|
1957
|
+
console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
|
|
1958
|
+
console.log();
|
|
1959
|
+
const done = state.pages.filter((p) => p.status === 'complete').length;
|
|
1960
|
+
const total = state.pages.length;
|
|
1961
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
1962
|
+
console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
|
|
1963
|
+
console.log();
|
|
1964
|
+
for (let i = 0; i < state.pages.length; i++) {
|
|
1965
|
+
const p = state.pages[i];
|
|
1966
|
+
let icon;
|
|
1967
|
+
let color;
|
|
1968
|
+
if (p.status === 'complete') {
|
|
1969
|
+
icon = '✓';
|
|
1970
|
+
color = chalk.green;
|
|
1971
|
+
}
|
|
1972
|
+
else if (p.status === 'in-progress') {
|
|
1973
|
+
icon = '→';
|
|
1974
|
+
color = chalk.bold.cyan;
|
|
1975
|
+
}
|
|
1976
|
+
else {
|
|
1977
|
+
icon = '○';
|
|
1978
|
+
color = chalk.dim;
|
|
1979
|
+
}
|
|
1980
|
+
const extra = p.status === 'complete'
|
|
1981
|
+
? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
|
|
1982
|
+
: '';
|
|
1983
|
+
console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
|
|
1984
|
+
}
|
|
1985
|
+
if (state.sharedComponents.length > 0) {
|
|
1986
|
+
console.log();
|
|
1987
|
+
console.log(chalk.bold('Shared Components:'));
|
|
1988
|
+
for (const sc of state.sharedComponents) {
|
|
1989
|
+
console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
console.log();
|
|
1993
|
+
}
|
|
1994
|
+
// ─── Migration Command Dispatcher ───────────────────────────────────
|
|
1995
|
+
function handleMigrateCommand(root, subArg) {
|
|
1996
|
+
// No subcommand or explicit empty → run initial survey
|
|
1997
|
+
if (!subArg) {
|
|
1998
|
+
const migState = readMigrationState(root);
|
|
1999
|
+
if (migState?.status === 'in-progress') {
|
|
2000
|
+
// Already in progress — show status and resume hint
|
|
2001
|
+
printMigrationStatus(root);
|
|
2002
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2003
|
+
if (page) {
|
|
2004
|
+
const editorState = readState(root);
|
|
2005
|
+
const currentStep = editorState?.migration ? editorState.step : 1;
|
|
2006
|
+
console.log(chalk.green('Continue with: ') +
|
|
2007
|
+
chalk.bold(`codeyam editor migrate ${currentStep}`));
|
|
2008
|
+
console.log();
|
|
2009
|
+
}
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
if (migState?.status === 'complete') {
|
|
2013
|
+
console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
printMigrateSurvey(root);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
// Parse subcommand
|
|
2020
|
+
if (subArg === 'next') {
|
|
2021
|
+
const result = advanceToNextPage(root);
|
|
2022
|
+
if (!result) {
|
|
2023
|
+
const migState = readMigrationState(root);
|
|
2024
|
+
if (migState?.status === 'complete') {
|
|
2025
|
+
console.log();
|
|
2026
|
+
console.log(chalk.bold.green('🎉 Migration complete!'));
|
|
2027
|
+
console.log();
|
|
2028
|
+
const done = migState.pages.filter((p) => p.status === 'complete');
|
|
2029
|
+
const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
|
|
2030
|
+
const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
|
|
2031
|
+
const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
|
|
2032
|
+
console.log(` Pages migrated: ${done.length}`);
|
|
2033
|
+
console.log(` Components extracted: ${totalComponents}`);
|
|
2034
|
+
console.log(` Functions extracted: ${totalFunctions}`);
|
|
2035
|
+
console.log(` Scenarios created: ${totalScenarios}`);
|
|
2036
|
+
console.log(` Shared components: ${migState.sharedComponents.length}`);
|
|
2037
|
+
console.log();
|
|
2038
|
+
console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
|
|
2039
|
+
console.log();
|
|
2040
|
+
}
|
|
2041
|
+
else {
|
|
2042
|
+
console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
|
|
2043
|
+
}
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
// Start M1 for the next page
|
|
2047
|
+
printMigrateStep1(root);
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (subArg === 'status') {
|
|
2051
|
+
printMigrationStatus(root);
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
if (subArg === 'complete') {
|
|
2055
|
+
const migState = readMigrationState(root);
|
|
2056
|
+
if (!migState) {
|
|
2057
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
2058
|
+
process.exit(1);
|
|
2059
|
+
}
|
|
2060
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2061
|
+
if (!page) {
|
|
2062
|
+
console.error(chalk.red('Error: No active migration page.'));
|
|
2063
|
+
process.exit(1);
|
|
2064
|
+
}
|
|
2065
|
+
completePage(root, {
|
|
2066
|
+
extractedComponents: page.extractedComponents,
|
|
2067
|
+
extractedFunctions: page.extractedFunctions,
|
|
2068
|
+
scenarioCount: page.scenarioCount,
|
|
2069
|
+
});
|
|
2070
|
+
console.log(chalk.green(`Page "${page.name}" marked complete.`));
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
// Numeric step: 1-8
|
|
2074
|
+
const step = parseInt(subArg, 10);
|
|
2075
|
+
if (isNaN(step) || step < 1 || step > 10) {
|
|
2076
|
+
console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
|
|
2077
|
+
process.exit(1);
|
|
2078
|
+
}
|
|
2079
|
+
// Ensure migration is active and first page started
|
|
2080
|
+
const migState = readMigrationState(root);
|
|
2081
|
+
if (!migState) {
|
|
2082
|
+
console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
|
|
2083
|
+
process.exit(1);
|
|
2084
|
+
}
|
|
2085
|
+
// If still in survey state and step 1 requested, start first page
|
|
2086
|
+
if (migState.status === 'surveyed' && step === 1) {
|
|
2087
|
+
migState.status = 'in-progress';
|
|
2088
|
+
migState.pages[0].status = 'in-progress';
|
|
2089
|
+
migState.pages[0].startedAt = new Date().toISOString();
|
|
2090
|
+
writeMigrationState(root, migState);
|
|
2091
|
+
}
|
|
2092
|
+
const stepFns = {
|
|
2093
|
+
1: printMigrateStep1,
|
|
2094
|
+
2: printMigrateStep2,
|
|
2095
|
+
3: printMigrateStep3,
|
|
2096
|
+
4: printMigrateStep4,
|
|
2097
|
+
5: printMigrateStep5,
|
|
2098
|
+
6: printMigrateStep6,
|
|
2099
|
+
7: printMigrateStep7,
|
|
2100
|
+
8: printMigrateStep8,
|
|
2101
|
+
9: printMigrateStep9,
|
|
2102
|
+
10: printMigrateStep10,
|
|
2103
|
+
};
|
|
2104
|
+
stepFns[step](root);
|
|
2105
|
+
}
|
|
2106
|
+
// ─── Step 14: Commit ─────────────────────────────────────────────────
|
|
2107
|
+
function printStep14(root, feature) {
|
|
2108
|
+
const prevState = readState(root);
|
|
2109
|
+
const isResuming = prevState?.step === 14;
|
|
2110
|
+
const now = new Date().toISOString();
|
|
2111
|
+
writeState(root, {
|
|
2112
|
+
feature,
|
|
2113
|
+
step: 14,
|
|
2114
|
+
label: STEP_LABELS[14],
|
|
2115
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
2116
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2117
|
+
});
|
|
2118
|
+
logEvent(root, 'step', { step: 14, label: 'Commit', feature });
|
|
2119
|
+
stepHeader(14, 'Commit', feature);
|
|
2120
|
+
if (isResuming) {
|
|
2121
|
+
printResumptionHeader(14);
|
|
2122
|
+
}
|
|
2123
|
+
console.log('Commit all changes for this feature.');
|
|
2124
|
+
console.log();
|
|
2125
|
+
console.log(chalk.bold('Checklist:'));
|
|
2126
|
+
checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
|
|
2127
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
2128
|
+
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
2129
|
+
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
2130
|
+
stopGate(14);
|
|
2131
|
+
}
|
|
2132
|
+
// ─── Step 15: Finalize ───────────────────────────────────────────────
|
|
2133
|
+
function printStep15(root, feature) {
|
|
2134
|
+
const prevState = readState(root);
|
|
2135
|
+
const isResuming = prevState?.step === 15;
|
|
2136
|
+
const now = new Date().toISOString();
|
|
2137
|
+
writeState(root, {
|
|
2138
|
+
feature,
|
|
2139
|
+
step: 15,
|
|
2140
|
+
label: STEP_LABELS[15],
|
|
2141
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
2142
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2143
|
+
});
|
|
2144
|
+
logEvent(root, 'step', { step: 15, label: 'Finalize', feature });
|
|
2145
|
+
stepHeader(15, 'Finalize', feature);
|
|
2146
|
+
if (isResuming) {
|
|
2147
|
+
printResumptionHeader(15);
|
|
2148
|
+
}
|
|
2149
|
+
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
2150
|
+
console.log();
|
|
2151
|
+
console.log(chalk.bold('Checklist:'));
|
|
2152
|
+
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
2153
|
+
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
2154
|
+
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
2155
|
+
stopGate(15);
|
|
2156
|
+
}
|
|
2157
|
+
// ─── Step 16: Push ───────────────────────────────────────────────────
|
|
2158
|
+
function printStep16(root, feature) {
|
|
2159
|
+
const prevState = readState(root);
|
|
2160
|
+
const isResuming = prevState?.step === 16;
|
|
2161
|
+
const now = new Date().toISOString();
|
|
2162
|
+
writeState(root, {
|
|
2163
|
+
feature,
|
|
2164
|
+
step: 16,
|
|
2165
|
+
label: STEP_LABELS[16],
|
|
2166
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
2167
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2168
|
+
});
|
|
2169
|
+
logEvent(root, 'step', { step: 16, label: 'Push', feature });
|
|
2170
|
+
stepHeader(16, 'Push', feature);
|
|
2171
|
+
if (isResuming) {
|
|
2172
|
+
printResumptionHeader(16);
|
|
2173
|
+
}
|
|
2174
|
+
console.log('Push the commit to the remote repository.');
|
|
2175
|
+
console.log();
|
|
2176
|
+
console.log(chalk.bold('Checklist:'));
|
|
2177
|
+
checkbox('Check if a git remote is configured: `git remote -v`');
|
|
2178
|
+
checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
|
|
2179
|
+
console.log(chalk.dim(' If a remote exists, ask (AskUserQuestion): "Would you like me to push this commit to the remote?" with options "Yes, push" and "No, skip"'));
|
|
2180
|
+
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2181
|
+
console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
|
|
2182
|
+
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2183
|
+
checkbox('After the user responds, run: `codeyam editor steps` to start the next feature');
|
|
2184
|
+
console.log();
|
|
2185
|
+
console.log(chalk.red.bold(' If the user reports a bug or requests a fix after committing:'));
|
|
2186
|
+
console.log(chalk.red.bold(' You MUST still run `codeyam editor change` before making any changes.'));
|
|
2187
|
+
console.log(chalk.red.bold(' The change workflow applies to ALL changes — post-commit fixes are not an exception.'));
|
|
2188
|
+
stopGate(16, { confirm: true });
|
|
2189
|
+
}
|
|
2190
|
+
// ─── Command definition ───────────────────────────────────────────────
|
|
2191
|
+
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
2192
|
+
/**
|
|
2193
|
+
* `codeyam editor analyze-imports`
|
|
2194
|
+
*
|
|
2195
|
+
* Runs data-structure-only analysis for all glossary entities, then outputs
|
|
2196
|
+
* an import graph and entity data structures as JSON to stdout.
|
|
2197
|
+
*/
|
|
2198
|
+
async function handleAnalyzeImports(options = {}) {
|
|
2199
|
+
const root = getProjectRoot();
|
|
2200
|
+
// Read glossary
|
|
2201
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2202
|
+
if (!fs.existsSync(glossaryPath)) {
|
|
2203
|
+
if (options.silent) {
|
|
2204
|
+
// Internal caller — glossary doesn't exist yet, nothing to analyze
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
1260
2207
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
1261
2208
|
console.error(chalk.dim(' Run codeyam editor 6 to create the glossary first.'));
|
|
1262
2209
|
process.exit(1);
|
|
1263
2210
|
}
|
|
1264
2211
|
let glossaryEntries;
|
|
1265
2212
|
try {
|
|
1266
|
-
|
|
2213
|
+
const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2214
|
+
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
1267
2215
|
}
|
|
1268
2216
|
catch {
|
|
1269
2217
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
1270
2218
|
process.exit(1);
|
|
1271
2219
|
}
|
|
1272
|
-
if (
|
|
2220
|
+
if (glossaryEntries.length === 0) {
|
|
1273
2221
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
1274
2222
|
process.exit(1);
|
|
1275
2223
|
}
|
|
1276
2224
|
const filePaths = glossaryEntries.map((e) => e.filePath);
|
|
1277
|
-
|
|
2225
|
+
// Include page files so pages get analyzed as entities too.
|
|
2226
|
+
// This allows the import graph to map page names to their dependencies.
|
|
2227
|
+
// Use allFiles (not map values) to include ALL pages — multiple routes
|
|
2228
|
+
// can share a page name (e.g., /feedback/[id] and /feedback/new both
|
|
2229
|
+
// map to "Feedback") but each needs its own entity analysis.
|
|
2230
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
2231
|
+
const { allFiles: allPageFiles } = scanPageFilePaths(root);
|
|
2232
|
+
for (const pageFile of allPageFiles) {
|
|
2233
|
+
if (!filePaths.includes(pageFile)) {
|
|
2234
|
+
filePaths.push(pageFile);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
// Include file paths from editor scenarios (both component_path and
|
|
2238
|
+
// page_file_path) so that all components and pages with scenarios get
|
|
2239
|
+
// entities — critical for non-Next.js apps where scanPageFilePaths
|
|
2240
|
+
// doesn't find page.tsx files.
|
|
2241
|
+
try {
|
|
2242
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2243
|
+
const db = getDatabase();
|
|
2244
|
+
const scenarioFiles = await db
|
|
2245
|
+
.selectFrom('editor_scenarios')
|
|
2246
|
+
.select(['component_path', 'page_file_path'])
|
|
2247
|
+
.distinct()
|
|
2248
|
+
.execute();
|
|
2249
|
+
for (const row of scenarioFiles) {
|
|
2250
|
+
const r = row;
|
|
2251
|
+
for (const fp of [r.component_path, r.page_file_path]) {
|
|
2252
|
+
if (fp && !filePaths.includes(fp)) {
|
|
2253
|
+
filePaths.push(fp);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
catch {
|
|
2259
|
+
// Non-fatal — scenario file paths just won't be included
|
|
2260
|
+
}
|
|
1278
2261
|
const progress = new ProgressReporter();
|
|
1279
|
-
// Run data-structure-only analysis for all entities
|
|
1280
|
-
|
|
2262
|
+
// Run data-structure-only analysis for all entities (glossary + pages)
|
|
2263
|
+
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2264
|
+
// The analyzer will discover and create them from file paths.
|
|
2265
|
+
progress.start('Running import analysis for all entities...');
|
|
1281
2266
|
try {
|
|
1282
2267
|
await runAnalysisForEntities({
|
|
1283
2268
|
projectRoot: root,
|
|
1284
2269
|
filePaths,
|
|
1285
|
-
entityNames,
|
|
1286
2270
|
progress,
|
|
1287
2271
|
onlyDataStructure: true,
|
|
1288
2272
|
});
|
|
@@ -1300,7 +2284,9 @@ async function handleAnalyzeImports() {
|
|
|
1300
2284
|
const entities = await loadEntities({});
|
|
1301
2285
|
if (!entities || entities.length === 0) {
|
|
1302
2286
|
progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
|
|
1303
|
-
|
|
2287
|
+
if (!options.silent) {
|
|
2288
|
+
console.log(JSON.stringify({ imports: {}, entities: {} }));
|
|
2289
|
+
}
|
|
1304
2290
|
return;
|
|
1305
2291
|
}
|
|
1306
2292
|
// Deduplicate to latest versions
|
|
@@ -1315,7 +2301,7 @@ async function handleAnalyzeImports() {
|
|
|
1315
2301
|
latestByKey.set(key, entity);
|
|
1316
2302
|
}
|
|
1317
2303
|
}
|
|
1318
|
-
const entityNameSet = new Set(
|
|
2304
|
+
const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
|
|
1319
2305
|
const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
|
|
1320
2306
|
// Build import graph from importedExports metadata
|
|
1321
2307
|
const imports = {};
|
|
@@ -1353,9 +2339,34 @@ async function handleAnalyzeImports() {
|
|
|
1353
2339
|
};
|
|
1354
2340
|
}
|
|
1355
2341
|
progress.succeed('Done');
|
|
1356
|
-
// Output combined JSON
|
|
1357
|
-
|
|
1358
|
-
|
|
2342
|
+
// Output combined JSON (suppressed when called internally during startup)
|
|
2343
|
+
if (!options.silent) {
|
|
2344
|
+
const summary = formatApiSubcommandResult('analyze-imports', {
|
|
2345
|
+
imports,
|
|
2346
|
+
entities: entityData,
|
|
2347
|
+
});
|
|
2348
|
+
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
2349
|
+
}
|
|
2350
|
+
// Backfill entity_sha on scenarios registered before entities existed.
|
|
2351
|
+
// This fixes the "missing data" UI state when scenarios were registered
|
|
2352
|
+
// while analyze-imports was still running in the background.
|
|
2353
|
+
try {
|
|
2354
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2355
|
+
const db = getDatabase();
|
|
2356
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
|
|
2357
|
+
sha: e.sha,
|
|
2358
|
+
name: e.name,
|
|
2359
|
+
filePath: e.filePath || '',
|
|
2360
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
2361
|
+
e.metadata?.namedExport === false,
|
|
2362
|
+
})));
|
|
2363
|
+
if (backfillResult.updated > 0 && !options.silent) {
|
|
2364
|
+
console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
catch {
|
|
2368
|
+
/* non-fatal */
|
|
2369
|
+
}
|
|
1359
2370
|
}
|
|
1360
2371
|
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
1361
2372
|
/**
|
|
@@ -1394,6 +2405,24 @@ function handleValidateSeed(jsonArg) {
|
|
|
1394
2405
|
: 0;
|
|
1395
2406
|
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
1396
2407
|
}
|
|
2408
|
+
// Check seed keys against Prisma schema if available
|
|
2409
|
+
const schemaPath = path.join(root, 'prisma', 'schema.prisma');
|
|
2410
|
+
try {
|
|
2411
|
+
if (fs.existsSync(schemaPath)) {
|
|
2412
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
2413
|
+
const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
|
|
2414
|
+
if (warnings.length > 0) {
|
|
2415
|
+
console.log();
|
|
2416
|
+
console.log(chalk.yellow('Prisma model name warnings:'));
|
|
2417
|
+
for (const w of warnings) {
|
|
2418
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
catch {
|
|
2424
|
+
// Schema not readable — skip Prisma validation
|
|
2425
|
+
}
|
|
1397
2426
|
}
|
|
1398
2427
|
else {
|
|
1399
2428
|
console.error(chalk.red('Seed data validation failed:'));
|
|
@@ -1403,6 +2432,27 @@ function handleValidateSeed(jsonArg) {
|
|
|
1403
2432
|
process.exit(1);
|
|
1404
2433
|
}
|
|
1405
2434
|
}
|
|
2435
|
+
// ─── Delete subcommand ────────────────────────────────────────────────
|
|
2436
|
+
/**
|
|
2437
|
+
* `codeyam editor delete <scenarioId>`
|
|
2438
|
+
*
|
|
2439
|
+
* Delete a scenario by ID via the running editor server.
|
|
2440
|
+
*/
|
|
2441
|
+
async function handleDelete(scenarioId) {
|
|
2442
|
+
if (!scenarioId) {
|
|
2443
|
+
console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
|
|
2444
|
+
process.exit(1);
|
|
2445
|
+
}
|
|
2446
|
+
const port = getServerPort();
|
|
2447
|
+
const result = await deleteScenarioViaCli(scenarioId, port);
|
|
2448
|
+
if (result.success) {
|
|
2449
|
+
console.log(chalk.green(result.message));
|
|
2450
|
+
}
|
|
2451
|
+
else {
|
|
2452
|
+
console.error(chalk.red(result.message));
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
1406
2456
|
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
1407
2457
|
/**
|
|
1408
2458
|
* `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
|
|
@@ -1538,6 +2588,17 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
1538
2588
|
}
|
|
1539
2589
|
return parts.join(' ');
|
|
1540
2590
|
}
|
|
2591
|
+
case 'analyze-imports': {
|
|
2592
|
+
const importEntries = Object.entries(data.imports || {});
|
|
2593
|
+
const entityEntries = Object.entries(data.entities || {});
|
|
2594
|
+
const parts = [];
|
|
2595
|
+
parts.push(`entities=${entityEntries.length}`);
|
|
2596
|
+
parts.push(`withImports=${importEntries.length}`);
|
|
2597
|
+
if (importEntries.length > 0) {
|
|
2598
|
+
parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
|
|
2599
|
+
}
|
|
2600
|
+
return parts.join(' ');
|
|
2601
|
+
}
|
|
1541
2602
|
default:
|
|
1542
2603
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
1543
2604
|
}
|
|
@@ -1550,8 +2611,9 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
1550
2611
|
*/
|
|
1551
2612
|
async function handleRegister(jsonArg) {
|
|
1552
2613
|
if (!jsonArg) {
|
|
2614
|
+
const { defaultName: dim } = getProjectDimensions(getProjectRoot());
|
|
1553
2615
|
console.error(chalk.red('Error: JSON argument required.'));
|
|
1554
|
-
console.error(chalk.dim(
|
|
2616
|
+
console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
|
|
1555
2617
|
console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
|
|
1556
2618
|
process.exit(1);
|
|
1557
2619
|
}
|
|
@@ -1563,57 +2625,138 @@ async function handleRegister(jsonArg) {
|
|
|
1563
2625
|
}
|
|
1564
2626
|
process.exit(1);
|
|
1565
2627
|
}
|
|
1566
|
-
|
|
2628
|
+
// Normalize to array for uniform handling
|
|
2629
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
1567
2630
|
const port = getServerPort();
|
|
1568
2631
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
});
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
parts
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
2632
|
+
const isBatch = items.length > 1;
|
|
2633
|
+
let succeeded = 0;
|
|
2634
|
+
let failed = 0;
|
|
2635
|
+
for (let i = 0; i < items.length; i++) {
|
|
2636
|
+
const body = items[i];
|
|
2637
|
+
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
2638
|
+
try {
|
|
2639
|
+
const res = await fetch(url, {
|
|
2640
|
+
method: 'POST',
|
|
2641
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2642
|
+
body: JSON.stringify(body),
|
|
2643
|
+
});
|
|
2644
|
+
const data = await res.json();
|
|
2645
|
+
// Print concise summary instead of raw JSON so Claude doesn't need python3
|
|
2646
|
+
const parts = [];
|
|
2647
|
+
parts.push(`success=${data.success}`);
|
|
2648
|
+
if (data.scenario?.name)
|
|
2649
|
+
parts.push(`name="${data.scenario.name}"`);
|
|
2650
|
+
parts.push(`screenshot=${!!data.screenshotCaptured}`);
|
|
2651
|
+
if (data.capturedViewport) {
|
|
2652
|
+
parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
|
|
2653
|
+
}
|
|
2654
|
+
if (data.scenario?.dimension)
|
|
2655
|
+
parts.push(`dimension="${data.scenario.dimension}"`);
|
|
2656
|
+
if (data.clientErrors?.length > 0) {
|
|
2657
|
+
parts.push(chalk.red(`errors=${data.clientErrors.length}`));
|
|
2658
|
+
}
|
|
2659
|
+
else {
|
|
2660
|
+
parts.push(`errors=0`);
|
|
2661
|
+
}
|
|
2662
|
+
if (data.seedResult)
|
|
2663
|
+
parts.push(`seed=${data.seedResult.success}`);
|
|
2664
|
+
if (data.captureError)
|
|
2665
|
+
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
2666
|
+
console.log(prefix + parts.join(' '));
|
|
2667
|
+
// Warn when application scenario is missing seed data
|
|
2668
|
+
if (data.missingSeedWarning) {
|
|
2669
|
+
console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
|
|
2670
|
+
console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
|
|
2671
|
+
console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
|
|
2672
|
+
console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
|
|
2673
|
+
}
|
|
2674
|
+
// Surface client errors prominently so they can't be missed
|
|
2675
|
+
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
2676
|
+
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
2677
|
+
for (const err of data.clientErrors) {
|
|
2678
|
+
console.log(chalk.red(` → ${err}`));
|
|
2679
|
+
}
|
|
2680
|
+
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
2681
|
+
// Provide actionable debugging hints for HTTP/API errors
|
|
2682
|
+
const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
|
|
2683
|
+
const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
|
|
2684
|
+
if (hasHttpError || hasApiResponseError) {
|
|
2685
|
+
console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
|
|
2686
|
+
console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
|
|
2687
|
+
console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
|
|
2688
|
+
}
|
|
2689
|
+
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2690
|
+
}
|
|
2691
|
+
if (!res.ok) {
|
|
2692
|
+
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2693
|
+
failed++;
|
|
2694
|
+
}
|
|
2695
|
+
else {
|
|
2696
|
+
succeeded++;
|
|
1601
2697
|
}
|
|
1602
|
-
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
1603
|
-
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
1604
2698
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2699
|
+
catch (error) {
|
|
2700
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2701
|
+
console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
|
|
2702
|
+
console.error(chalk.dim(` ${msg}`));
|
|
2703
|
+
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
2704
|
+
failed++;
|
|
1608
2705
|
}
|
|
1609
2706
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
2707
|
+
if (isBatch) {
|
|
2708
|
+
console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
|
|
2709
|
+
}
|
|
2710
|
+
if (failed > 0) {
|
|
2711
|
+
process.exit(1);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
// ─── Glossary-add subcommand ──────────────────────────────────────────
|
|
2715
|
+
/**
|
|
2716
|
+
* `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
|
|
2717
|
+
*
|
|
2718
|
+
* Safely adds/updates entries in .codeyam/glossary.json via the CLI,
|
|
2719
|
+
* avoiding hand-editing that breaks on unicode characters.
|
|
2720
|
+
*/
|
|
2721
|
+
async function handleGlossaryAdd(jsonArg) {
|
|
2722
|
+
if (!jsonArg) {
|
|
2723
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
2724
|
+
console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
|
|
2725
|
+
console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
|
|
2726
|
+
process.exit(1);
|
|
2727
|
+
}
|
|
2728
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
2729
|
+
if (parsed.error) {
|
|
2730
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
2731
|
+
process.exit(1);
|
|
2732
|
+
}
|
|
2733
|
+
// Normalize to array
|
|
2734
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2735
|
+
// Read existing glossary
|
|
2736
|
+
const root = getProjectRoot();
|
|
2737
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2738
|
+
let existing = [];
|
|
2739
|
+
try {
|
|
2740
|
+
const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2741
|
+
existing = sanitizeGlossaryEntries(raw);
|
|
2742
|
+
}
|
|
2743
|
+
catch {
|
|
2744
|
+
// Glossary doesn't exist yet or can't be parsed — start fresh
|
|
2745
|
+
}
|
|
2746
|
+
// Merge
|
|
2747
|
+
const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
|
|
2748
|
+
const result = mergeGlossaryEntries(existing, items);
|
|
2749
|
+
// Report validation errors
|
|
2750
|
+
for (const err of result.errors) {
|
|
2751
|
+
console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
|
|
2752
|
+
}
|
|
2753
|
+
if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
|
|
1615
2754
|
process.exit(1);
|
|
1616
2755
|
}
|
|
2756
|
+
// Write back with utf8 encoding to safely handle unicode
|
|
2757
|
+
fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
|
|
2758
|
+
fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
|
|
2759
|
+
console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
|
|
1617
2760
|
}
|
|
1618
2761
|
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
1619
2762
|
/**
|
|
@@ -1632,11 +2775,23 @@ async function handleDependents(entityName) {
|
|
|
1632
2775
|
const progress = new ProgressReporter();
|
|
1633
2776
|
progress.start('Loading entities from database...');
|
|
1634
2777
|
await initializeEnvironment();
|
|
1635
|
-
|
|
2778
|
+
let allEntities = await loadEntities({});
|
|
1636
2779
|
if (!allEntities || allEntities.length === 0) {
|
|
1637
|
-
progress.
|
|
1638
|
-
|
|
1639
|
-
|
|
2780
|
+
progress.succeed('No entities found — running analyze-imports first...');
|
|
2781
|
+
try {
|
|
2782
|
+
await handleAnalyzeImports({ silent: true });
|
|
2783
|
+
}
|
|
2784
|
+
catch {
|
|
2785
|
+
console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
2786
|
+
process.exit(1);
|
|
2787
|
+
}
|
|
2788
|
+
// Reload entities after analysis
|
|
2789
|
+
const reloaded = await loadEntities({});
|
|
2790
|
+
if (!reloaded || reloaded.length === 0) {
|
|
2791
|
+
progress.fail('No entities found even after analyze-imports');
|
|
2792
|
+
process.exit(1);
|
|
2793
|
+
}
|
|
2794
|
+
allEntities = reloaded;
|
|
1640
2795
|
}
|
|
1641
2796
|
// Find the target entity by name (case-insensitive match)
|
|
1642
2797
|
const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
@@ -1723,15 +2878,16 @@ async function handleDependents(entityName) {
|
|
|
1723
2878
|
* `codeyam editor change <feature>`
|
|
1724
2879
|
*
|
|
1725
2880
|
* Prints a condensed post-change checklist that guides Claude through
|
|
1726
|
-
* re-verifying after user-requested modifications.
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
2881
|
+
* re-verifying after user-requested modifications. When called from
|
|
2882
|
+
* step 13+, this loops back to step 13 (present). When called from an
|
|
2883
|
+
* earlier step, it returns to that step so the normal flow continues.
|
|
1729
2884
|
*/
|
|
1730
2885
|
function handleChange(feature) {
|
|
1731
2886
|
const root = getProjectRoot();
|
|
2887
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
2888
|
+
const state = readState(root);
|
|
1732
2889
|
if (!feature) {
|
|
1733
2890
|
// Try to read feature from state
|
|
1734
|
-
const state = readState(root);
|
|
1735
2891
|
if (state?.feature) {
|
|
1736
2892
|
feature = state.feature;
|
|
1737
2893
|
}
|
|
@@ -1741,6 +2897,7 @@ function handleChange(feature) {
|
|
|
1741
2897
|
process.exit(1);
|
|
1742
2898
|
}
|
|
1743
2899
|
}
|
|
2900
|
+
const currentStep = state?.step ?? 13;
|
|
1744
2901
|
const port = getServerPort();
|
|
1745
2902
|
console.log();
|
|
1746
2903
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -1748,9 +2905,19 @@ function handleChange(feature) {
|
|
|
1748
2905
|
console.log();
|
|
1749
2906
|
console.log('The user has requested changes. Follow this checklist after making them.');
|
|
1750
2907
|
console.log();
|
|
2908
|
+
const designSystem = readDesignSystem(root);
|
|
2909
|
+
if (designSystem) {
|
|
2910
|
+
console.log(chalk.bold.magenta('Design System (active):'));
|
|
2911
|
+
console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
|
|
2912
|
+
console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
|
|
2913
|
+
console.log();
|
|
2914
|
+
console.log(designSystem);
|
|
2915
|
+
console.log();
|
|
2916
|
+
}
|
|
1751
2917
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
1752
2918
|
console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
|
|
1753
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
2919
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2920
|
+
printDimensionGuidance(dim, dimNames);
|
|
1754
2921
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
1755
2922
|
console.log();
|
|
1756
2923
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
@@ -1758,7 +2925,7 @@ function handleChange(feature) {
|
|
|
1758
2925
|
console.log();
|
|
1759
2926
|
console.log(chalk.bold('1. Re-register affected component scenarios:'));
|
|
1760
2927
|
checkbox('For each component you modified, re-register ALL its scenarios');
|
|
1761
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName"}'`));
|
|
2928
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","componentName":"ComponentName","componentPath":"app/components/ComponentName.tsx","url":"/isolated-components/ComponentName?s=ScenarioName","dimensions":["${dim}"]}'`));
|
|
1762
2929
|
checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
|
|
1763
2930
|
console.log();
|
|
1764
2931
|
console.log(chalk.bold('2. Re-run affected tests:'));
|
|
@@ -1769,7 +2936,6 @@ function handleChange(feature) {
|
|
|
1769
2936
|
console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
|
|
1770
2937
|
console.log();
|
|
1771
2938
|
console.log(chalk.bold('3. Re-capture app-level scenarios:'));
|
|
1772
|
-
checkbox('Run `codeyam editor analyze-imports` to refresh the import graph after code changes');
|
|
1773
2939
|
checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
|
|
1774
2940
|
checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
|
|
1775
2941
|
checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
|
|
@@ -1777,15 +2943,23 @@ function handleChange(feature) {
|
|
|
1777
2943
|
console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
|
|
1778
2944
|
console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
|
|
1779
2945
|
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2946
|
+
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2947
|
+
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
2948
|
+
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2949
|
+
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
1780
2950
|
console.log();
|
|
1781
2951
|
console.log(chalk.bold('4. Verify completeness:'));
|
|
1782
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1783
|
-
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
|
|
2952
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
2953
|
+
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2954
|
+
printDimensionGuidance(dim, dimNames);
|
|
2955
|
+
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
2956
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1784
2957
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
1785
2958
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
1786
2959
|
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
1787
2960
|
console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
|
|
1788
2961
|
checkbox('Fix any errors, then re-register affected scenarios');
|
|
2962
|
+
checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
|
|
1789
2963
|
console.log();
|
|
1790
2964
|
console.log(chalk.bold('5. Update the existing journal entry:'));
|
|
1791
2965
|
checkbox('Get existing entry: `codeyam editor journal-list`');
|
|
@@ -1794,14 +2968,173 @@ function handleChange(feature) {
|
|
|
1794
2968
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
1795
2969
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
1796
2970
|
console.log();
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2971
|
+
// If the change was initiated from a step before 13, return to that step
|
|
2972
|
+
// instead of jumping to step 13. The change workflow should only loop to
|
|
2973
|
+
// step 13 when changes are requested FROM step 13.
|
|
2974
|
+
if (currentStep < 13) {
|
|
2975
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2976
|
+
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2977
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2978
|
+
chalk.bold(`codeyam editor ${currentStep}`));
|
|
2979
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2980
|
+
}
|
|
2981
|
+
else {
|
|
2982
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2983
|
+
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2984
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2985
|
+
chalk.bold(`codeyam editor 13`));
|
|
2986
|
+
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2987
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2988
|
+
}
|
|
1803
2989
|
console.log();
|
|
1804
2990
|
}
|
|
2991
|
+
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2992
|
+
/**
|
|
2993
|
+
* Fetch the audit result from the server.
|
|
2994
|
+
* Returns null if the server is unreachable.
|
|
2995
|
+
*/
|
|
2996
|
+
async function fetchAuditResult() {
|
|
2997
|
+
const port = getServerPort();
|
|
2998
|
+
try {
|
|
2999
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3000
|
+
if (!res.ok)
|
|
3001
|
+
return null;
|
|
3002
|
+
return await res.json();
|
|
3003
|
+
}
|
|
3004
|
+
catch {
|
|
3005
|
+
return null;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
3010
|
+
* false if any issues remain or the server is unreachable.
|
|
3011
|
+
* Used as a hard gate before steps 8+.
|
|
3012
|
+
*
|
|
3013
|
+
* Auto-runs analyze-imports if the only failure is incomplete entities,
|
|
3014
|
+
* then re-checks once.
|
|
3015
|
+
*/
|
|
3016
|
+
async function checkAuditGate() {
|
|
3017
|
+
const data = await fetchAuditResult();
|
|
3018
|
+
if (!data)
|
|
3019
|
+
return true; // Server unreachable — don't block
|
|
3020
|
+
if (data.summary?.allPassing === true)
|
|
3021
|
+
return true;
|
|
3022
|
+
// If incomplete entities are the only issue, auto-fix with analyze-imports (once)
|
|
3023
|
+
const { isAutoRemediable } = await import('../utils/editorAudit.js');
|
|
3024
|
+
if (isAutoRemediable(data.summary, false)) {
|
|
3025
|
+
try {
|
|
3026
|
+
await handleAnalyzeImports({ silent: true });
|
|
3027
|
+
}
|
|
3028
|
+
catch {
|
|
3029
|
+
return false;
|
|
3030
|
+
}
|
|
3031
|
+
// Re-check after fix
|
|
3032
|
+
const retry = await fetchAuditResult();
|
|
3033
|
+
if (retry?.summary?.allPassing === true)
|
|
3034
|
+
return true;
|
|
3035
|
+
}
|
|
3036
|
+
// Print specific failures so Claude knows what to fix without running audit separately
|
|
3037
|
+
printAuditGateFailures(data);
|
|
3038
|
+
return false;
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Print a concise summary of audit failures for the gate block message.
|
|
3042
|
+
* This gives Claude immediate context about what to fix without needing
|
|
3043
|
+
* to run `codeyam editor audit` as a separate step.
|
|
3044
|
+
*/
|
|
3045
|
+
function printAuditGateFailures(data) {
|
|
3046
|
+
const s = data.summary;
|
|
3047
|
+
if (!s)
|
|
3048
|
+
return;
|
|
3049
|
+
const issues = [];
|
|
3050
|
+
if (s.componentsMissing > 0) {
|
|
3051
|
+
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
3052
|
+
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3053
|
+
for (const c of missing) {
|
|
3054
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
if (s.componentsWithErrors > 0) {
|
|
3058
|
+
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
3059
|
+
const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
|
|
3060
|
+
for (const c of withErrors) {
|
|
3061
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
if (s.functionsMissing > 0) {
|
|
3065
|
+
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3066
|
+
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3067
|
+
for (const f of missing) {
|
|
3068
|
+
issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
if (s.functionsFailing > 0) {
|
|
3072
|
+
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
3073
|
+
const failing = (data.functions || []).filter((f) => f.status === 'failing');
|
|
3074
|
+
for (const f of failing) {
|
|
3075
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
if (s.functionsRunnerError > 0) {
|
|
3079
|
+
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
3080
|
+
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3081
|
+
for (const f of runnerErrors) {
|
|
3082
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
if (s.functionsNameMismatch > 0) {
|
|
3086
|
+
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
3087
|
+
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3088
|
+
for (const f of mismatch) {
|
|
3089
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (s.missingFromGlossary > 0)
|
|
3093
|
+
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3094
|
+
if (s.incompleteEntities > 0) {
|
|
3095
|
+
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3096
|
+
if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3097
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3098
|
+
}
|
|
3099
|
+
else if (preCount > 0) {
|
|
3100
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
|
|
3101
|
+
}
|
|
3102
|
+
else {
|
|
3103
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (s.miscategorizedScenarios > 0)
|
|
3107
|
+
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3108
|
+
if (s.scenariosNeedingRecapture > 0)
|
|
3109
|
+
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
3110
|
+
if (issues.length > 0) {
|
|
3111
|
+
console.error(chalk.yellow('\nAudit failures:'));
|
|
3112
|
+
for (const issue of issues) {
|
|
3113
|
+
console.error(chalk.yellow(` • ${issue}`));
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
// Surface client error details inline — these are the most common cause of
|
|
3117
|
+
// Claude looping because the errors require code fixes, not audit re-runs.
|
|
3118
|
+
if (data.components) {
|
|
3119
|
+
const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
|
|
3120
|
+
if (withErrors.length > 0) {
|
|
3121
|
+
console.error(chalk.yellow('\nClient errors found:'));
|
|
3122
|
+
for (const c of withErrors) {
|
|
3123
|
+
console.error(chalk.red(` ${c.name}:`));
|
|
3124
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3125
|
+
console.error(chalk.red(` → ${err}`));
|
|
3126
|
+
}
|
|
3127
|
+
if (c.clientErrors.length > 3) {
|
|
3128
|
+
console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3132
|
+
console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
|
|
3133
|
+
console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
3137
|
+
}
|
|
1805
3138
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
1806
3139
|
/**
|
|
1807
3140
|
* `codeyam editor audit`
|
|
@@ -1811,42 +3144,74 @@ function handleChange(feature) {
|
|
|
1811
3144
|
* have test files. Exits with code 1 if anything is missing.
|
|
1812
3145
|
*/
|
|
1813
3146
|
async function handleAudit() {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
let data;
|
|
1817
|
-
try {
|
|
1818
|
-
const res = await fetch(url);
|
|
1819
|
-
if (!res.ok) {
|
|
1820
|
-
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
1821
|
-
process.exit(1);
|
|
1822
|
-
}
|
|
1823
|
-
data = await res.json();
|
|
1824
|
-
}
|
|
1825
|
-
catch (err) {
|
|
3147
|
+
let data = await fetchAuditResult();
|
|
3148
|
+
if (!data) {
|
|
1826
3149
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
1827
|
-
console.error(chalk.dim(` ${err.message}`));
|
|
1828
3150
|
process.exit(1);
|
|
1829
3151
|
}
|
|
3152
|
+
// Auto-fix incomplete entities — but only once.
|
|
3153
|
+
// If analyze-imports runs and entities are STILL incomplete, report clearly
|
|
3154
|
+
// instead of retrying. The analysis may have errors (e.g., stale entity
|
|
3155
|
+
// references from refactoring) that can't be fixed by re-running.
|
|
3156
|
+
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3157
|
+
let autoRemediationFailed = false;
|
|
3158
|
+
if (incompleteBeforeFix.length > 0) {
|
|
3159
|
+
console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
|
|
3160
|
+
try {
|
|
3161
|
+
await handleAnalyzeImports({ silent: true });
|
|
3162
|
+
}
|
|
3163
|
+
catch {
|
|
3164
|
+
// Fall through — the audit will still report them as incomplete
|
|
3165
|
+
}
|
|
3166
|
+
// Re-fetch audit results after the fix
|
|
3167
|
+
data = await fetchAuditResult();
|
|
3168
|
+
if (!data) {
|
|
3169
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
|
|
3170
|
+
process.exit(1);
|
|
3171
|
+
}
|
|
3172
|
+
// If entities are still incomplete after running analyze-imports,
|
|
3173
|
+
// flag it so we can show a clear message instead of looping
|
|
3174
|
+
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3175
|
+
if (incompleteAfterFix.length > 0) {
|
|
3176
|
+
autoRemediationFailed = true;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
1830
3179
|
const { components, functions, summary } = data;
|
|
1831
3180
|
console.log();
|
|
1832
3181
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
1833
3182
|
console.log();
|
|
1834
3183
|
// Components
|
|
1835
3184
|
if (components.length > 0) {
|
|
3185
|
+
// Build name frequency map for disambiguation
|
|
3186
|
+
const componentNameCounts = new Map();
|
|
3187
|
+
for (const c of components) {
|
|
3188
|
+
componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
|
|
3189
|
+
}
|
|
1836
3190
|
console.log(chalk.bold('Components (scenarios):'));
|
|
1837
3191
|
for (const c of components) {
|
|
1838
|
-
const icon = c.status === 'ok'
|
|
3192
|
+
const icon = c.status === 'ok'
|
|
3193
|
+
? chalk.green('✓')
|
|
3194
|
+
: c.status === 'needs_recapture'
|
|
3195
|
+
? chalk.yellow('↻')
|
|
3196
|
+
: chalk.red('✗');
|
|
1839
3197
|
let detail;
|
|
1840
3198
|
if (c.status === 'has_errors') {
|
|
1841
3199
|
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
1842
3200
|
}
|
|
3201
|
+
else if (c.status === 'needs_recapture') {
|
|
3202
|
+
detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
|
|
3203
|
+
}
|
|
1843
3204
|
else if (c.status === 'ok') {
|
|
1844
3205
|
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
1845
3206
|
}
|
|
1846
3207
|
else {
|
|
1847
3208
|
detail = chalk.red(' — no scenarios registered');
|
|
1848
3209
|
}
|
|
1849
|
-
|
|
3210
|
+
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3211
|
+
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
3212
|
+
const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
|
|
3213
|
+
const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
|
|
3214
|
+
console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
|
|
1850
3215
|
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
1851
3216
|
for (const err of c.clientErrors.slice(0, 3)) {
|
|
1852
3217
|
console.log(chalk.red(` → ${err}`));
|
|
@@ -1854,6 +3219,14 @@ async function handleAudit() {
|
|
|
1854
3219
|
if (c.clientErrors.length > 3) {
|
|
1855
3220
|
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
1856
3221
|
}
|
|
3222
|
+
// Detect browser API errors and provide actionable guidance
|
|
3223
|
+
const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
|
|
3224
|
+
const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
|
|
3225
|
+
if (hasBrowserApiErrors) {
|
|
3226
|
+
console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
|
|
3227
|
+
console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
|
|
3228
|
+
console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
|
|
3229
|
+
}
|
|
1857
3230
|
}
|
|
1858
3231
|
}
|
|
1859
3232
|
console.log();
|
|
@@ -1868,6 +3241,14 @@ async function handleAudit() {
|
|
|
1868
3241
|
case 'ok':
|
|
1869
3242
|
detail = chalk.dim(` (${f.testFile})`);
|
|
1870
3243
|
break;
|
|
3244
|
+
case 'runner_error':
|
|
3245
|
+
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3246
|
+
if (f.errorMessage) {
|
|
3247
|
+
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3248
|
+
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3249
|
+
detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
|
|
3250
|
+
}
|
|
3251
|
+
break;
|
|
1871
3252
|
case 'failing':
|
|
1872
3253
|
detail = chalk.red(` — tests failing: ${f.testFile}`);
|
|
1873
3254
|
break;
|
|
@@ -1885,6 +3266,79 @@ async function handleAudit() {
|
|
|
1885
3266
|
}
|
|
1886
3267
|
console.log();
|
|
1887
3268
|
}
|
|
3269
|
+
// Missing from glossary
|
|
3270
|
+
const missingFromGlossary = data.missingFromGlossary || [];
|
|
3271
|
+
if (missingFromGlossary.length > 0) {
|
|
3272
|
+
console.log(chalk.bold('Missing from glossary:'));
|
|
3273
|
+
for (const m of missingFromGlossary) {
|
|
3274
|
+
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3275
|
+
}
|
|
3276
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3277
|
+
console.log();
|
|
3278
|
+
}
|
|
3279
|
+
// Incomplete entities (scenarios without analyses)
|
|
3280
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3281
|
+
if (incompleteEntities.length > 0) {
|
|
3282
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3283
|
+
for (const e of incompleteEntities) {
|
|
3284
|
+
console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
|
|
3285
|
+
}
|
|
3286
|
+
if (autoRemediationFailed) {
|
|
3287
|
+
console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
|
|
3288
|
+
console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
|
|
3289
|
+
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3290
|
+
console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
|
|
3291
|
+
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3292
|
+
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3293
|
+
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3294
|
+
console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
|
|
3295
|
+
}
|
|
3296
|
+
else {
|
|
3297
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
|
|
3298
|
+
}
|
|
3299
|
+
console.log();
|
|
3300
|
+
}
|
|
3301
|
+
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
3302
|
+
const miscategorizedScenarios = data.miscategorizedScenarios || [];
|
|
3303
|
+
if (miscategorizedScenarios.length > 0) {
|
|
3304
|
+
console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
|
|
3305
|
+
for (const m of miscategorizedScenarios) {
|
|
3306
|
+
console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
|
|
3307
|
+
for (const name of m.scenarioNames) {
|
|
3308
|
+
console.log(chalk.dim(` "${name}"`));
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
|
|
3312
|
+
console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
|
|
3313
|
+
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
3314
|
+
console.log();
|
|
3315
|
+
}
|
|
3316
|
+
// Scenarios needing recapture (entity or dependency tree changed)
|
|
3317
|
+
const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
|
|
3318
|
+
if (scenariosNeedingRecapture.length > 0) {
|
|
3319
|
+
console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
|
|
3320
|
+
for (const s of scenariosNeedingRecapture) {
|
|
3321
|
+
const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
|
|
3322
|
+
? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
|
|
3323
|
+
: `${s.status.status}`;
|
|
3324
|
+
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3325
|
+
}
|
|
3326
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
3327
|
+
console.log();
|
|
3328
|
+
}
|
|
3329
|
+
// Duplicate glossary names (warning, not a failure)
|
|
3330
|
+
const duplicateNames = data.duplicateNames || [];
|
|
3331
|
+
if (duplicateNames.length > 0) {
|
|
3332
|
+
console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
|
|
3333
|
+
for (const dn of duplicateNames) {
|
|
3334
|
+
console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
|
|
3335
|
+
for (const fp of dn.filePaths) {
|
|
3336
|
+
console.log(` ${fp}`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
|
|
3340
|
+
console.log();
|
|
3341
|
+
}
|
|
1888
3342
|
// Summary
|
|
1889
3343
|
const allOk = summary.allPassing;
|
|
1890
3344
|
if (allOk) {
|
|
@@ -1905,15 +3359,144 @@ async function handleAudit() {
|
|
|
1905
3359
|
if (summary.functionsFailing > 0) {
|
|
1906
3360
|
parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
|
|
1907
3361
|
}
|
|
3362
|
+
if (summary.functionsRunnerError > 0) {
|
|
3363
|
+
parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
|
|
3364
|
+
}
|
|
1908
3365
|
if (summary.functionsNameMismatch > 0) {
|
|
1909
3366
|
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
1910
3367
|
}
|
|
3368
|
+
if (summary.missingFromGlossary > 0) {
|
|
3369
|
+
parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
|
|
3370
|
+
}
|
|
3371
|
+
if (summary.incompleteEntities > 0) {
|
|
3372
|
+
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3373
|
+
}
|
|
3374
|
+
if (summary.miscategorizedScenarios > 0) {
|
|
3375
|
+
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3376
|
+
}
|
|
3377
|
+
if (summary.scenariosNeedingRecapture > 0) {
|
|
3378
|
+
parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
|
|
3379
|
+
}
|
|
1911
3380
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
1912
3381
|
}
|
|
1913
3382
|
console.log();
|
|
1914
3383
|
if (!allOk) {
|
|
1915
3384
|
process.exit(1);
|
|
1916
3385
|
}
|
|
3386
|
+
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3387
|
+
// so the App tab can show component/function dependencies for each page.
|
|
3388
|
+
// Previously this was a manual step that Claude had to remember to run.
|
|
3389
|
+
console.log(chalk.dim('Building import graph...'));
|
|
3390
|
+
try {
|
|
3391
|
+
await handleAnalyzeImports({ silent: true });
|
|
3392
|
+
}
|
|
3393
|
+
catch {
|
|
3394
|
+
// Non-fatal — audit passed, import graph is a bonus
|
|
3395
|
+
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3399
|
+
/**
|
|
3400
|
+
* `codeyam editor recapture-stale`
|
|
3401
|
+
*
|
|
3402
|
+
* Identifies all scenarios whose entity (or dependency) has changed but
|
|
3403
|
+
* whose screenshot hasn't been recaptured this session, then recaptures
|
|
3404
|
+
* them in batch.
|
|
3405
|
+
*/
|
|
3406
|
+
async function handleRecaptureStale() {
|
|
3407
|
+
const port = getServerPort();
|
|
3408
|
+
const url = `http://localhost:${port}/api/editor-recapture-stale`;
|
|
3409
|
+
console.log();
|
|
3410
|
+
console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
|
|
3411
|
+
console.log();
|
|
3412
|
+
let res;
|
|
3413
|
+
try {
|
|
3414
|
+
res = await fetch(url, { method: 'POST' });
|
|
3415
|
+
}
|
|
3416
|
+
catch (err) {
|
|
3417
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3418
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
3419
|
+
process.exit(1);
|
|
3420
|
+
}
|
|
3421
|
+
if (!res.ok) {
|
|
3422
|
+
const body = await res.json().catch(() => null);
|
|
3423
|
+
console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
|
|
3424
|
+
if (body?.error)
|
|
3425
|
+
console.error(chalk.red(` ${body.error}`));
|
|
3426
|
+
process.exit(1);
|
|
3427
|
+
}
|
|
3428
|
+
// Handle JSON response (early returns like "no changes" / "all up to date")
|
|
3429
|
+
const contentType = res.headers.get('content-type') || '';
|
|
3430
|
+
if (contentType.includes('application/json')) {
|
|
3431
|
+
const data = await res.json();
|
|
3432
|
+
if (data.note) {
|
|
3433
|
+
console.log(chalk.dim(data.note));
|
|
3434
|
+
}
|
|
3435
|
+
else if (data.total === 0) {
|
|
3436
|
+
console.log(chalk.green('All scenarios are up to date.'));
|
|
3437
|
+
}
|
|
3438
|
+
console.log();
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
// Stream NDJSON progress events
|
|
3442
|
+
let total = 0;
|
|
3443
|
+
let recapturedCount = 0;
|
|
3444
|
+
let failedCount = 0;
|
|
3445
|
+
const reader = res.body?.getReader();
|
|
3446
|
+
if (!reader) {
|
|
3447
|
+
console.error(chalk.red('Error: No response body'));
|
|
3448
|
+
process.exit(1);
|
|
3449
|
+
}
|
|
3450
|
+
const decoder = new TextDecoder();
|
|
3451
|
+
let buffer = '';
|
|
3452
|
+
while (true) {
|
|
3453
|
+
const { done, value } = await reader.read();
|
|
3454
|
+
if (done)
|
|
3455
|
+
break;
|
|
3456
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3457
|
+
const lines = buffer.split('\n');
|
|
3458
|
+
buffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
3459
|
+
for (const line of lines) {
|
|
3460
|
+
if (!line.trim())
|
|
3461
|
+
continue;
|
|
3462
|
+
try {
|
|
3463
|
+
const event = JSON.parse(line);
|
|
3464
|
+
switch (event.type) {
|
|
3465
|
+
case 'start':
|
|
3466
|
+
total = event.total;
|
|
3467
|
+
console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
|
|
3468
|
+
break;
|
|
3469
|
+
case 'capturing':
|
|
3470
|
+
process.stdout.write(chalk.dim(` … ${event.name}`));
|
|
3471
|
+
break;
|
|
3472
|
+
case 'success':
|
|
3473
|
+
// Clear the "capturing" line and print success
|
|
3474
|
+
process.stdout.write('\r\x1b[K');
|
|
3475
|
+
console.log(` ${chalk.green('✓')} ${event.name}`);
|
|
3476
|
+
recapturedCount++;
|
|
3477
|
+
break;
|
|
3478
|
+
case 'failure':
|
|
3479
|
+
process.stdout.write('\r\x1b[K');
|
|
3480
|
+
console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
|
|
3481
|
+
failedCount++;
|
|
3482
|
+
break;
|
|
3483
|
+
case 'done':
|
|
3484
|
+
// Final summary
|
|
3485
|
+
console.log();
|
|
3486
|
+
const color = failedCount > 0 ? chalk.yellow : chalk.green;
|
|
3487
|
+
console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
|
|
3488
|
+
console.log();
|
|
3489
|
+
break;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
catch {
|
|
3493
|
+
// Skip unparseable lines
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
if (failedCount > 0) {
|
|
3498
|
+
process.exit(1);
|
|
3499
|
+
}
|
|
1917
3500
|
}
|
|
1918
3501
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
1919
3502
|
async function handleScenarios() {
|
|
@@ -2001,6 +3584,103 @@ async function handleScenarios() {
|
|
|
2001
3584
|
const total = scenarios.length;
|
|
2002
3585
|
const groupCount = groups.size;
|
|
2003
3586
|
console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
|
|
3587
|
+
// Show screenshot paths so Claude can verify images with the Read tool
|
|
3588
|
+
const withScreenshots = scenarios.filter((s) => s.screenshotPath);
|
|
3589
|
+
if (withScreenshots.length > 0) {
|
|
3590
|
+
console.log();
|
|
3591
|
+
console.log(chalk.bold.cyan('Screenshots:'));
|
|
3592
|
+
for (const s of withScreenshots) {
|
|
3593
|
+
console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
console.log();
|
|
3597
|
+
}
|
|
3598
|
+
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
3599
|
+
async function handleScenarioCoverage() {
|
|
3600
|
+
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3601
|
+
try {
|
|
3602
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3603
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
3604
|
+
const db = getDatabase();
|
|
3605
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3606
|
+
if (backfillCount > 0) {
|
|
3607
|
+
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3608
|
+
await handleAnalyzeImports({ silent: true });
|
|
3609
|
+
// Run backfill after analysis
|
|
3610
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
3611
|
+
const entities = await loadEntities({});
|
|
3612
|
+
if (entities && entities.length > 0) {
|
|
3613
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3614
|
+
sha: e.sha,
|
|
3615
|
+
name: e.name,
|
|
3616
|
+
filePath: e.filePath || '',
|
|
3617
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3618
|
+
e.metadata?.namedExport === false,
|
|
3619
|
+
})));
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
catch {
|
|
3624
|
+
// Non-fatal — proceed with coverage check regardless
|
|
3625
|
+
}
|
|
3626
|
+
const port = getServerPort();
|
|
3627
|
+
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
3628
|
+
let data;
|
|
3629
|
+
try {
|
|
3630
|
+
const res = await fetch(url);
|
|
3631
|
+
if (!res.ok) {
|
|
3632
|
+
console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
|
|
3633
|
+
process.exit(1);
|
|
3634
|
+
}
|
|
3635
|
+
data = await res.json();
|
|
3636
|
+
}
|
|
3637
|
+
catch (err) {
|
|
3638
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3639
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
3640
|
+
process.exit(1);
|
|
3641
|
+
}
|
|
3642
|
+
console.log();
|
|
3643
|
+
console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
|
|
3644
|
+
console.log();
|
|
3645
|
+
if (data.note) {
|
|
3646
|
+
console.log(chalk.yellow(data.note));
|
|
3647
|
+
console.log();
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
// Fresh scenarios (already recaptured this feature)
|
|
3651
|
+
if (data.freshScenarios.length > 0) {
|
|
3652
|
+
console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
|
|
3653
|
+
for (const s of data.freshScenarios) {
|
|
3654
|
+
console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
|
|
3655
|
+
}
|
|
3656
|
+
console.log();
|
|
3657
|
+
}
|
|
3658
|
+
// Stale scenarios (need recapturing)
|
|
3659
|
+
if (data.staleScenarios.length > 0) {
|
|
3660
|
+
console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
|
|
3661
|
+
for (const s of data.staleScenarios) {
|
|
3662
|
+
console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
|
|
3663
|
+
}
|
|
3664
|
+
console.log();
|
|
3665
|
+
console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
|
|
3666
|
+
console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
|
|
3667
|
+
console.log();
|
|
3668
|
+
}
|
|
3669
|
+
// Uncovered pages (changed but no scenarios at all)
|
|
3670
|
+
if (data.uncoveredPages.length > 0) {
|
|
3671
|
+
console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
|
|
3672
|
+
for (const p of data.uncoveredPages) {
|
|
3673
|
+
console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
|
|
3674
|
+
}
|
|
3675
|
+
console.log();
|
|
3676
|
+
}
|
|
3677
|
+
// Summary
|
|
3678
|
+
if (data.pass) {
|
|
3679
|
+
console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
|
|
3680
|
+
}
|
|
3681
|
+
else {
|
|
3682
|
+
console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
|
|
3683
|
+
}
|
|
2004
3684
|
console.log();
|
|
2005
3685
|
}
|
|
2006
3686
|
// ─── Template subcommand ─────────────────────────────────────────────
|
|
@@ -2090,6 +3770,22 @@ async function handleTemplate() {
|
|
|
2090
3770
|
// Config parse error is non-fatal
|
|
2091
3771
|
}
|
|
2092
3772
|
}
|
|
3773
|
+
// 5b. Mark the project as template-scaffolded so migration detection
|
|
3774
|
+
// doesn't treat it as a pre-existing project that needs migration.
|
|
3775
|
+
const now = new Date().toISOString();
|
|
3776
|
+
const existingState = readState(root);
|
|
3777
|
+
writeState(root, {
|
|
3778
|
+
feature: '',
|
|
3779
|
+
step: 0,
|
|
3780
|
+
label: '',
|
|
3781
|
+
startedAt: now,
|
|
3782
|
+
featureStartedAt: now,
|
|
3783
|
+
...existingState,
|
|
3784
|
+
scaffolded: true,
|
|
3785
|
+
});
|
|
3786
|
+
// 5c. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
3787
|
+
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
3788
|
+
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
2093
3789
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
2094
3790
|
console.log(chalk.bold('Refreshing editor...'));
|
|
2095
3791
|
try {
|
|
@@ -2108,17 +3804,14 @@ async function handleTemplate() {
|
|
|
2108
3804
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
2109
3805
|
/**
|
|
2110
3806
|
* `codeyam editor sync`
|
|
2111
|
-
* Import scenarios from scenarios
|
|
3807
|
+
* Import scenarios from editor-scenarios/ files into the local database.
|
|
3808
|
+
* Falls back to legacy scenarios-manifest.json if no _metadata files found.
|
|
2112
3809
|
*/
|
|
2113
3810
|
async function handleSync() {
|
|
2114
3811
|
const root = getProjectRoot();
|
|
2115
|
-
const
|
|
2116
|
-
if (
|
|
2117
|
-
console.log(chalk.yellow('No
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (manifest.scenarios.length === 0) {
|
|
2121
|
-
console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
|
|
3812
|
+
const entries = scanScenarioFiles(root);
|
|
3813
|
+
if (entries.length === 0) {
|
|
3814
|
+
console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
|
|
2122
3815
|
return;
|
|
2123
3816
|
}
|
|
2124
3817
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
@@ -2142,13 +3835,12 @@ async function handleSync() {
|
|
|
2142
3835
|
const { project } = await requireBranchAndProject(projectSlug);
|
|
2143
3836
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2144
3837
|
const db = getDatabase();
|
|
2145
|
-
// Fetch existing editor scenarios
|
|
2146
3838
|
const existingRows = await db
|
|
2147
3839
|
.selectFrom('editor_scenarios')
|
|
2148
3840
|
.select(['id', 'updated_at'])
|
|
2149
3841
|
.where('project_id', '=', project.id)
|
|
2150
3842
|
.execute();
|
|
2151
|
-
const result = await
|
|
3843
|
+
const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
|
|
2152
3844
|
await db
|
|
2153
3845
|
.insertInto('editor_scenarios')
|
|
2154
3846
|
.values(row)
|
|
@@ -2173,6 +3865,34 @@ async function handleSync() {
|
|
|
2173
3865
|
parts.push(`${result.skipped} unchanged`);
|
|
2174
3866
|
console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
|
|
2175
3867
|
}
|
|
3868
|
+
// Migrate legacy scenario formats after sync, then re-sync if anything was fixed
|
|
3869
|
+
try {
|
|
3870
|
+
const migrateResult = migrateScenarioFormats(root);
|
|
3871
|
+
if (migrateResult.fixed > 0) {
|
|
3872
|
+
console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
3873
|
+
// Re-sync so the DB reflects the corrected file metadata
|
|
3874
|
+
const refreshedRows = await db
|
|
3875
|
+
.selectFrom('editor_scenarios')
|
|
3876
|
+
.select(['id', 'updated_at'])
|
|
3877
|
+
.where('project_id', '=', project.id)
|
|
3878
|
+
.execute();
|
|
3879
|
+
await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
|
|
3880
|
+
await db
|
|
3881
|
+
.insertInto('editor_scenarios')
|
|
3882
|
+
.values(row)
|
|
3883
|
+
.execute();
|
|
3884
|
+
}, async (id, row) => {
|
|
3885
|
+
await db
|
|
3886
|
+
.updateTable('editor_scenarios')
|
|
3887
|
+
.set(row)
|
|
3888
|
+
.where('id', '=', id)
|
|
3889
|
+
.execute();
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
catch {
|
|
3894
|
+
// Non-fatal
|
|
3895
|
+
}
|
|
2176
3896
|
}
|
|
2177
3897
|
// ─── Verify Images subcommand ─────────────────────────────────────────
|
|
2178
3898
|
async function handleVerifyImages(jsonArg) {
|
|
@@ -2305,8 +4025,11 @@ function handleEditorDebug(args) {
|
|
|
2305
4025
|
11: printStep11,
|
|
2306
4026
|
12: printStep12,
|
|
2307
4027
|
13: printStep13,
|
|
4028
|
+
14: printStep14,
|
|
4029
|
+
15: printStep15,
|
|
4030
|
+
16: printStep16,
|
|
2308
4031
|
};
|
|
2309
|
-
for (let step = 1; step <=
|
|
4032
|
+
for (let step = 1; step <= 16; step++) {
|
|
2310
4033
|
const stepId = `step-${step}`;
|
|
2311
4034
|
if (!wants(stepId))
|
|
2312
4035
|
continue;
|
|
@@ -2385,8 +4108,8 @@ const editorCommand = {
|
|
|
2385
4108
|
describe: 'Editor mode guided workflow',
|
|
2386
4109
|
builder: (yargs) => {
|
|
2387
4110
|
const stepDescription = IS_INTERNAL_BUILD
|
|
2388
|
-
? 'Step number (1-
|
|
2389
|
-
: 'Step number (1-
|
|
4111
|
+
? 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
|
|
4112
|
+
: 'Step number (1-16) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
|
|
2390
4113
|
let builder = yargs
|
|
2391
4114
|
.positional('step', {
|
|
2392
4115
|
type: 'string',
|
|
@@ -2422,7 +4145,7 @@ const editorCommand = {
|
|
|
2422
4145
|
builder = builder
|
|
2423
4146
|
.option('target', {
|
|
2424
4147
|
type: 'string',
|
|
2425
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
4148
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-16, or comma-separated list)',
|
|
2426
4149
|
})
|
|
2427
4150
|
.option('resume', {
|
|
2428
4151
|
type: 'boolean',
|
|
@@ -2438,7 +4161,9 @@ const editorCommand = {
|
|
|
2438
4161
|
describe: 'Debug: output directory for the bundle',
|
|
2439
4162
|
});
|
|
2440
4163
|
}
|
|
2441
|
-
|
|
4164
|
+
// Allow extra positional args for subcommands like `isolate A B C`
|
|
4165
|
+
// without yargs rejecting them as unknown.
|
|
4166
|
+
return builder.strict(false);
|
|
2442
4167
|
},
|
|
2443
4168
|
handler: async (argv) => {
|
|
2444
4169
|
const root = getProjectRoot();
|
|
@@ -2477,6 +4202,11 @@ const editorCommand = {
|
|
|
2477
4202
|
await handleRegister(argv.json || '');
|
|
2478
4203
|
return;
|
|
2479
4204
|
}
|
|
4205
|
+
// Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
|
|
4206
|
+
if (argv.step === 'glossary-add') {
|
|
4207
|
+
await handleGlossaryAdd(argv.json || '');
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
2480
4210
|
// Subcommand: codeyam editor analyze-imports
|
|
2481
4211
|
if (argv.step === 'analyze-imports') {
|
|
2482
4212
|
await handleAnalyzeImports();
|
|
@@ -2497,6 +4227,16 @@ const editorCommand = {
|
|
|
2497
4227
|
await handleScenarios();
|
|
2498
4228
|
return;
|
|
2499
4229
|
}
|
|
4230
|
+
// Subcommand: codeyam editor scenario-coverage
|
|
4231
|
+
if (argv.step === 'scenario-coverage') {
|
|
4232
|
+
await handleScenarioCoverage();
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
// Subcommand: codeyam editor recapture-stale
|
|
4236
|
+
if (argv.step === 'recapture-stale') {
|
|
4237
|
+
await handleRecaptureStale();
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
2500
4240
|
// Subcommand: codeyam editor change <feature>
|
|
2501
4241
|
if (argv.step === 'change') {
|
|
2502
4242
|
handleChange(argv.json || '');
|
|
@@ -2512,14 +4252,16 @@ const editorCommand = {
|
|
|
2512
4252
|
await handleValidateSeed(argv.json || '');
|
|
2513
4253
|
return;
|
|
2514
4254
|
}
|
|
4255
|
+
// Subcommand: codeyam editor delete <scenarioId>
|
|
4256
|
+
if (argv.step === 'delete') {
|
|
4257
|
+
await handleDelete(argv.json || '');
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
2515
4260
|
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
4261
|
+
// Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
2516
4262
|
if (argv.step === 'isolate') {
|
|
2517
|
-
const
|
|
2518
|
-
|
|
2519
|
-
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
2520
|
-
// Collect any extra positional args (yargs puts them in argv._)
|
|
2521
|
-
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
2522
|
-
names.push(...extras);
|
|
4263
|
+
const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
|
|
4264
|
+
const names = parseIsolateArgs(argv.json, argv._);
|
|
2523
4265
|
handleIsolate(names);
|
|
2524
4266
|
return;
|
|
2525
4267
|
}
|
|
@@ -2542,6 +4284,11 @@ const editorCommand = {
|
|
|
2542
4284
|
await handleEditorDebug(argv);
|
|
2543
4285
|
return;
|
|
2544
4286
|
}
|
|
4287
|
+
// Subcommand: codeyam editor migrate [subArg]
|
|
4288
|
+
if (argv.step === 'migrate') {
|
|
4289
|
+
handleMigrateCommand(root, argv.json || undefined);
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
2545
4292
|
// Subcommand: codeyam editor steps — show setup or cycle overview
|
|
2546
4293
|
if (argv.step === 'steps') {
|
|
2547
4294
|
if (!hasProject(root)) {
|
|
@@ -2549,9 +4296,9 @@ const editorCommand = {
|
|
|
2549
4296
|
}
|
|
2550
4297
|
else {
|
|
2551
4298
|
const state = readState(root);
|
|
2552
|
-
// Clear prompt file when feature is done (step
|
|
4299
|
+
// Clear prompt file when feature is done (step 16) so the hook
|
|
2553
4300
|
// can capture the next feature request from the user.
|
|
2554
|
-
if (state?.step ===
|
|
4301
|
+
if (state?.step === 16) {
|
|
2555
4302
|
clearEditorUserPrompt(root);
|
|
2556
4303
|
}
|
|
2557
4304
|
printCycleOverview(root, state);
|
|
@@ -2559,8 +4306,8 @@ const editorCommand = {
|
|
|
2559
4306
|
return;
|
|
2560
4307
|
}
|
|
2561
4308
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
2562
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
2563
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
4309
|
+
if (step != null && (isNaN(step) || step < 1 || step > 16)) {
|
|
4310
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-16.`));
|
|
2564
4311
|
process.exit(1);
|
|
2565
4312
|
}
|
|
2566
4313
|
if (step == null) {
|
|
@@ -2593,9 +4340,45 @@ const editorCommand = {
|
|
|
2593
4340
|
return;
|
|
2594
4341
|
}
|
|
2595
4342
|
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
2596
|
-
//
|
|
2597
|
-
|
|
2598
|
-
|
|
4343
|
+
// Backfill _metadata into existing scenario files that predate the embedded metadata feature.
|
|
4344
|
+
// This reads metadata from the DB and writes it into files that don't have _metadata yet.
|
|
4345
|
+
try {
|
|
4346
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4347
|
+
const db = getDatabase();
|
|
4348
|
+
const dbScenarios = await db
|
|
4349
|
+
.selectFrom('editor_scenarios')
|
|
4350
|
+
.select([
|
|
4351
|
+
'id',
|
|
4352
|
+
'name',
|
|
4353
|
+
'description',
|
|
4354
|
+
'component_name',
|
|
4355
|
+
'component_path',
|
|
4356
|
+
'url',
|
|
4357
|
+
'type',
|
|
4358
|
+
'screenshot_path',
|
|
4359
|
+
'viewport_width',
|
|
4360
|
+
'viewport_height',
|
|
4361
|
+
'dimensions',
|
|
4362
|
+
'screenshot_paths',
|
|
4363
|
+
'page_file_path',
|
|
4364
|
+
'created_at',
|
|
4365
|
+
'updated_at',
|
|
4366
|
+
])
|
|
4367
|
+
.where('project_id', '=', project.id)
|
|
4368
|
+
.execute();
|
|
4369
|
+
if (dbScenarios.length > 0) {
|
|
4370
|
+
const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
|
|
4371
|
+
if (backfilled > 0) {
|
|
4372
|
+
console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
catch {
|
|
4377
|
+
// Non-fatal — backfill is best-effort
|
|
4378
|
+
}
|
|
4379
|
+
// Auto-sync scenario files (with _metadata) to database
|
|
4380
|
+
const scenarioEntries = scanScenarioFiles(projectRoot);
|
|
4381
|
+
if (scenarioEntries.length > 0) {
|
|
2599
4382
|
try {
|
|
2600
4383
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2601
4384
|
const db = getDatabase();
|
|
@@ -2604,7 +4387,7 @@ const editorCommand = {
|
|
|
2604
4387
|
.select(['id', 'updated_at'])
|
|
2605
4388
|
.where('project_id', '=', project.id)
|
|
2606
4389
|
.execute();
|
|
2607
|
-
const syncResult = await
|
|
4390
|
+
const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
|
|
2608
4391
|
await db
|
|
2609
4392
|
.insertInto('editor_scenarios')
|
|
2610
4393
|
.values(row)
|
|
@@ -2622,13 +4405,85 @@ const editorCommand = {
|
|
|
2622
4405
|
parts.push(`${syncResult.inserted} imported`);
|
|
2623
4406
|
if (syncResult.updated > 0)
|
|
2624
4407
|
parts.push(`${syncResult.updated} updated`);
|
|
2625
|
-
console.log(chalk.green(` Synced scenarios from
|
|
4408
|
+
console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
|
|
2626
4409
|
}
|
|
2627
4410
|
}
|
|
2628
4411
|
catch {
|
|
2629
4412
|
// Non-fatal — sync failure shouldn't block editor startup
|
|
2630
4413
|
}
|
|
2631
4414
|
}
|
|
4415
|
+
// Migrate legacy scenario formats: resolve null viewports, populate
|
|
4416
|
+
// dimensions arrays, and build screenshotPaths maps from single values.
|
|
4417
|
+
// If files were fixed, re-sync to update the database with corrected values.
|
|
4418
|
+
try {
|
|
4419
|
+
const migrateResult = migrateScenarioFormats(projectRoot);
|
|
4420
|
+
if (migrateResult.fixed > 0) {
|
|
4421
|
+
console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
4422
|
+
// Re-sync so the DB reflects the fixed file metadata
|
|
4423
|
+
try {
|
|
4424
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4425
|
+
const db = getDatabase();
|
|
4426
|
+
const rows = await db
|
|
4427
|
+
.selectFrom('editor_scenarios')
|
|
4428
|
+
.select(['id', 'updated_at'])
|
|
4429
|
+
.where('project_id', '=', project.id)
|
|
4430
|
+
.execute();
|
|
4431
|
+
await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
|
|
4432
|
+
await db
|
|
4433
|
+
.insertInto('editor_scenarios')
|
|
4434
|
+
.values(row)
|
|
4435
|
+
.execute();
|
|
4436
|
+
}, async (id, row) => {
|
|
4437
|
+
await db
|
|
4438
|
+
.updateTable('editor_scenarios')
|
|
4439
|
+
.set(row)
|
|
4440
|
+
.where('id', '=', id)
|
|
4441
|
+
.execute();
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4444
|
+
catch {
|
|
4445
|
+
// Non-fatal — DB re-sync failure shouldn't block startup
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
catch {
|
|
4450
|
+
// Non-fatal — migration failure shouldn't block editor startup
|
|
4451
|
+
}
|
|
4452
|
+
// Auto-seed on fresh clone: if no scenario has ever been activated
|
|
4453
|
+
// (active-scenario.json doesn't exist), seed the application database
|
|
4454
|
+
// with the first application scenario that has seed data.
|
|
4455
|
+
const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
|
|
4456
|
+
if (!fs.existsSync(activeScenarioPath)) {
|
|
4457
|
+
try {
|
|
4458
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4459
|
+
const seedDb = getDb();
|
|
4460
|
+
const appScenario = await seedDb
|
|
4461
|
+
.selectFrom('editor_scenarios')
|
|
4462
|
+
.select(['id', 'name', 'type'])
|
|
4463
|
+
.where('project_id', '=', project.id)
|
|
4464
|
+
.where('type', '=', 'application')
|
|
4465
|
+
.orderBy('created_at', 'asc')
|
|
4466
|
+
.executeTakeFirst();
|
|
4467
|
+
if (appScenario) {
|
|
4468
|
+
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4469
|
+
if (fs.existsSync(seedFile)) {
|
|
4470
|
+
const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
|
|
4471
|
+
const seedResult = await switchActiveScenario({
|
|
4472
|
+
scenarioId: appScenario.id,
|
|
4473
|
+
scenarioName: appScenario.name || undefined,
|
|
4474
|
+
scenarioType: appScenario.type || undefined,
|
|
4475
|
+
projectRoot,
|
|
4476
|
+
});
|
|
4477
|
+
if (seedResult.seedResult?.success) {
|
|
4478
|
+
console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
catch {
|
|
4484
|
+
// Non-fatal — auto-seed is best-effort
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
2632
4487
|
// `codeyam editor` (no step) always implies editor mode.
|
|
2633
4488
|
// The empty-folder heuristic is no longer needed here — running this
|
|
2634
4489
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -2667,29 +4522,23 @@ const editorCommand = {
|
|
|
2667
4522
|
editorMode,
|
|
2668
4523
|
});
|
|
2669
4524
|
// Auto-finalize analyzer so codeyam analyze works
|
|
2670
|
-
if (editorMode
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
});
|
|
2684
|
-
execSync('npm run build', {
|
|
2685
|
-
cwd: templatePath,
|
|
2686
|
-
stdio: 'pipe',
|
|
2687
|
-
});
|
|
2688
|
-
fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
|
|
4525
|
+
if (editorMode) {
|
|
4526
|
+
const stepLabels = {
|
|
4527
|
+
'npm-install': 'Installing simulation dependencies...',
|
|
4528
|
+
'playwright-install': 'Installing browser (Chromium)...',
|
|
4529
|
+
build: 'Building simulation engine...',
|
|
4530
|
+
};
|
|
4531
|
+
const simProgress = new ProgressReporter();
|
|
4532
|
+
const finalization = ensureAnalyzerFinalized({
|
|
4533
|
+
onProgress: (step) => simProgress.start(stepLabels[step]),
|
|
4534
|
+
});
|
|
4535
|
+
if (finalization.errors.length > 0) {
|
|
4536
|
+
for (const err of finalization.errors) {
|
|
4537
|
+
simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
|
|
2689
4538
|
}
|
|
2690
4539
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
4540
|
+
else if (finalization.needed) {
|
|
4541
|
+
simProgress.succeed('Simulation engine ready');
|
|
2693
4542
|
}
|
|
2694
4543
|
}
|
|
2695
4544
|
// Start background server (handles killing existing servers internally)
|
|
@@ -2700,6 +4549,150 @@ const editorCommand = {
|
|
|
2700
4549
|
project,
|
|
2701
4550
|
branch,
|
|
2702
4551
|
});
|
|
4552
|
+
// Build import graph if glossary exists but entities are missing.
|
|
4553
|
+
// Runs on first startup (no entities at all) AND when page files or
|
|
4554
|
+
// scenario component files lack entity coverage.
|
|
4555
|
+
const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
|
|
4556
|
+
if (fs.existsSync(glossaryPath)) {
|
|
4557
|
+
let needsAnalysis = false;
|
|
4558
|
+
const entities = await loadEntities({});
|
|
4559
|
+
if (!entities || entities.length === 0) {
|
|
4560
|
+
needsAnalysis = true;
|
|
4561
|
+
}
|
|
4562
|
+
else {
|
|
4563
|
+
const entityFilePaths = new Set(entities.map((e) => e.filePath));
|
|
4564
|
+
// Check if any page files are missing entities (Next.js apps)
|
|
4565
|
+
try {
|
|
4566
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
4567
|
+
const { allFiles } = scanPageFilePaths(projectRoot);
|
|
4568
|
+
const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
|
|
4569
|
+
const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
|
|
4570
|
+
if (missingPages.length > 0) {
|
|
4571
|
+
console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
|
|
4572
|
+
needsAnalysis = true;
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
catch {
|
|
4576
|
+
// Non-fatal — page file check failed
|
|
4577
|
+
}
|
|
4578
|
+
// Check if any scenario files (component_path or page_file_path)
|
|
4579
|
+
// are missing entities — covers non-Next.js apps
|
|
4580
|
+
if (!needsAnalysis) {
|
|
4581
|
+
try {
|
|
4582
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4583
|
+
const db = getDatabase();
|
|
4584
|
+
const scenarioFiles = await db
|
|
4585
|
+
.selectFrom('editor_scenarios')
|
|
4586
|
+
.select(['component_path', 'page_file_path'])
|
|
4587
|
+
.where('project_id', '=', project.id)
|
|
4588
|
+
.distinct()
|
|
4589
|
+
.execute();
|
|
4590
|
+
const missingCount = scenarioFiles.filter((row) => {
|
|
4591
|
+
const cp = row.component_path;
|
|
4592
|
+
const pfp = row.page_file_path;
|
|
4593
|
+
return ((cp && !entityFilePaths.has(cp)) ||
|
|
4594
|
+
(pfp && !entityFilePaths.has(pfp)));
|
|
4595
|
+
}).length;
|
|
4596
|
+
if (missingCount > 0) {
|
|
4597
|
+
console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
|
|
4598
|
+
needsAnalysis = true;
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
catch {
|
|
4602
|
+
// Non-fatal
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
// Check if any scenarios have null entity_sha with file paths — heal on startup
|
|
4606
|
+
if (!needsAnalysis) {
|
|
4607
|
+
try {
|
|
4608
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4609
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4610
|
+
const db = getDatabase();
|
|
4611
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4612
|
+
if (backfillCount > 0) {
|
|
4613
|
+
console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
|
|
4614
|
+
needsAnalysis = true;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
catch {
|
|
4618
|
+
// Non-fatal
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
if (needsAnalysis) {
|
|
4623
|
+
try {
|
|
4624
|
+
await handleAnalyzeImports({ silent: true });
|
|
4625
|
+
}
|
|
4626
|
+
catch {
|
|
4627
|
+
// Non-fatal
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
// Backfill page_file_path for application scenarios that have a URL but
|
|
4632
|
+
// no page_file_path. This resolves the file from the URL using Next.js
|
|
4633
|
+
// routing conventions, enabling syncScenarioEntityShas to link them to
|
|
4634
|
+
// entities. Covers fresh clones where JSON files lack pageFilePath.
|
|
4635
|
+
try {
|
|
4636
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4637
|
+
const pfpDb = getDb();
|
|
4638
|
+
const unresolved = await pfpDb
|
|
4639
|
+
.selectFrom('editor_scenarios')
|
|
4640
|
+
.select(['id', 'url'])
|
|
4641
|
+
.where('project_id', '=', project.id)
|
|
4642
|
+
.where('component_name', 'is', null)
|
|
4643
|
+
.where('page_file_path', 'is', null)
|
|
4644
|
+
.where('url', 'is not', null)
|
|
4645
|
+
.execute();
|
|
4646
|
+
if (unresolved.length > 0) {
|
|
4647
|
+
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4648
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
4649
|
+
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4650
|
+
let pfpResolved = 0;
|
|
4651
|
+
for (const row of unresolved) {
|
|
4652
|
+
const r = row;
|
|
4653
|
+
if (!r.url)
|
|
4654
|
+
continue;
|
|
4655
|
+
const matched = matchUrlToPageFile(r.url, pfpFiles);
|
|
4656
|
+
if (matched) {
|
|
4657
|
+
await pfpDb
|
|
4658
|
+
.updateTable('editor_scenarios')
|
|
4659
|
+
.set({ page_file_path: matched })
|
|
4660
|
+
.where('id', '=', r.id)
|
|
4661
|
+
.execute();
|
|
4662
|
+
pfpResolved++;
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
if (pfpResolved > 0) {
|
|
4666
|
+
console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
catch {
|
|
4671
|
+
/* Non-fatal — page_file_path backfill from URL */
|
|
4672
|
+
}
|
|
4673
|
+
// Backfill entity_sha on scenarios that were synced before entities existed.
|
|
4674
|
+
// This runs independently of analyze-imports so fresh clones and file-synced
|
|
4675
|
+
// scenarios get linked to their entities even when auto-analyze doesn't trigger.
|
|
4676
|
+
try {
|
|
4677
|
+
const entities = await loadEntities({});
|
|
4678
|
+
if (entities && entities.length > 0) {
|
|
4679
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4680
|
+
const db = getDatabase();
|
|
4681
|
+
const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4682
|
+
sha: e.sha,
|
|
4683
|
+
name: e.name,
|
|
4684
|
+
filePath: e.filePath || '',
|
|
4685
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4686
|
+
e.metadata?.namedExport === false,
|
|
4687
|
+
})));
|
|
4688
|
+
if (result.updated > 0) {
|
|
4689
|
+
console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
catch {
|
|
4694
|
+
/* Non-fatal */
|
|
4695
|
+
}
|
|
2703
4696
|
console.log();
|
|
2704
4697
|
console.log(` Dashboard: ${url}`);
|
|
2705
4698
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -2713,7 +4706,9 @@ const editorCommand = {
|
|
|
2713
4706
|
: process.platform === 'win32'
|
|
2714
4707
|
? 'start ""'
|
|
2715
4708
|
: 'xdg-open';
|
|
2716
|
-
execSync(`${openCommand} "${url}/editor"`, {
|
|
4709
|
+
execSync(`${openCommand} "${url}/editor"`, {
|
|
4710
|
+
stdio: 'ignore',
|
|
4711
|
+
});
|
|
2717
4712
|
}
|
|
2718
4713
|
catch {
|
|
2719
4714
|
// Silently fail if open command doesn't work
|
|
@@ -2727,6 +4722,18 @@ const editorCommand = {
|
|
|
2727
4722
|
return;
|
|
2728
4723
|
}
|
|
2729
4724
|
const state = readState(root);
|
|
4725
|
+
// Validate step transition — prevent skipping steps.
|
|
4726
|
+
// Exception: step 2 with --feature is always allowed because step 1's
|
|
4727
|
+
// instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
|
|
4728
|
+
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4729
|
+
const skipValidation = step === 2 && argv.feature;
|
|
4730
|
+
if (!skipValidation) {
|
|
4731
|
+
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
4732
|
+
if (stepError) {
|
|
4733
|
+
console.error(chalk.red(`Error: ${stepError}`));
|
|
4734
|
+
process.exit(1);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
2730
4737
|
switch (step) {
|
|
2731
4738
|
case 1: {
|
|
2732
4739
|
const feature = argv.feature || undefined;
|
|
@@ -2758,12 +4765,25 @@ const editorCommand = {
|
|
|
2758
4765
|
case 10:
|
|
2759
4766
|
case 11:
|
|
2760
4767
|
case 12:
|
|
2761
|
-
case 13:
|
|
4768
|
+
case 13:
|
|
4769
|
+
case 14:
|
|
4770
|
+
case 15:
|
|
4771
|
+
case 16: {
|
|
2762
4772
|
const feature = argv.feature || state?.feature;
|
|
2763
4773
|
if (!feature) {
|
|
2764
4774
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
2765
4775
|
process.exit(1);
|
|
2766
4776
|
}
|
|
4777
|
+
// Hard gate: steps 8+ require audit to have passed
|
|
4778
|
+
if (step >= 8) {
|
|
4779
|
+
const auditOk = await checkAuditGate();
|
|
4780
|
+
if (!auditOk) {
|
|
4781
|
+
// checkAuditGate() already printed specific failure details above
|
|
4782
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4783
|
+
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4784
|
+
process.exit(1);
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
2767
4787
|
const stepFns = {
|
|
2768
4788
|
3: printStep3,
|
|
2769
4789
|
4: printStep4,
|
|
@@ -2776,6 +4796,9 @@ const editorCommand = {
|
|
|
2776
4796
|
11: printStep11,
|
|
2777
4797
|
12: printStep12,
|
|
2778
4798
|
13: printStep13,
|
|
4799
|
+
14: printStep14,
|
|
4800
|
+
15: printStep15,
|
|
4801
|
+
16: printStep16,
|
|
2779
4802
|
};
|
|
2780
4803
|
stepFns[step](root, feature);
|
|
2781
4804
|
break;
|