@codeyam/codeyam-cli 0.1.0-staging.2ea44f6 → 0.1.0-staging.30dc541
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 +3 -3
- 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 +99 -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 +8 -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 +101 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts +2 -0
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js +2 -0
- package/analyzer-template/packages/github/dist/types/src/enums/ProjectFramework.js.map +1 -1
- package/analyzer-template/packages/types/src/enums/ProjectFramework.ts +2 -0
- package/analyzer-template/packages/ui-components/package.json +1 -1
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts +2 -0
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js +2 -0
- package/analyzer-template/packages/utils/dist/types/src/enums/ProjectFramework.js.map +1 -1
- package/codeyam-cli/src/cli.js +24 -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 +3077 -509
- 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/data/techStacks.js +77 -0
- package/codeyam-cli/src/data/techStacks.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js +46 -0
- package/codeyam-cli/src/utils/__tests__/backgroundServer.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/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 +2201 -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__/editorDevServer.test.js +152 -3
- package/codeyam-cli/src/utils/__tests__/editorDevServer.test.js.map +1 -1
- 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__/editorGuardMiddleware.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js +140 -12
- package/codeyam-cli/src/utils/__tests__/editorImageVerifier.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js +238 -2
- package/codeyam-cli/src/utils/__tests__/editorJournal.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +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 +191 -5
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +153 -0
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js +139 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioLookup.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +291 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +1437 -2
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +280 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -0
- 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 +641 -45
- 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__/journalCaptureStabilization.test.js +122 -0
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +129 -0
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/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 +14 -5
- 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/__tests__/templateConsistency.test.js +51 -0
- package/codeyam-cli/src/utils/__tests__/templateConsistency.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +142 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -0
- package/codeyam-cli/src/utils/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 +93 -17
- 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 +410 -6
- 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/editorDevServer.js +89 -1
- package/codeyam-cli/src/utils/editorDevServer.js.map +1 -1
- 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/editorGuard.js +36 -0
- package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
- package/codeyam-cli/src/utils/editorImageVerifier.js +45 -10
- package/codeyam-cli/src/utils/editorImageVerifier.js.map +1 -1
- package/codeyam-cli/src/utils/editorJournal.js +78 -3
- package/codeyam-cli/src/utils/editorJournal.js.map +1 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +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 +43 -2
- 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 +134 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarios.js +488 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +422 -0
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -0
- 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 +89 -21
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +97 -8
- 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 +31 -0
- package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -0
- package/codeyam-cli/src/utils/progress.js +2 -2
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/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/serverState.js +30 -0
- package/codeyam-cli/src/utils/serverState.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +14 -5
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +17 -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/utils/webappDetection.js +21 -0
- package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
- 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 +628 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +228 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +79 -0
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.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-C76mRRiF.js → EntityItem-Crt_KN_U.js} +5 -5
- package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CobE682z.js → EntityTypeIcon-CD7lGABo.js} +9 -9
- 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-djPLI-WV.js → ReportIssueModal-C2PLkej3.js} +4 -4
- package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-DanvyBPb.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-ZlRKbhrq.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-C96V0n15.js → _index-BAWd-Xjf.js} +4 -4
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BpKzcsJz.js → activity.(_tab)-BOARiB-g.js} +8 -8
- 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-D9hemwl6.js → agent-transcripts-Bg3e7q4S.js} +7 -7
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-project-info-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-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-D_nMCFmP.js → book-open-CL-lMgHh.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BH2h1Ea2.js → chevron-down-GmAjGS9-.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-DyIKORY6.js → circle-check-DFcQkN5j.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{copy-NDbZjXao.js → copy-C6iF61Xs.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/createLucideIcon-4ImjHTVC.js +41 -0
- 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)-CGzKlIHg.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._-DItJnD8s.js → entity._sha._-Blfy9UlN.js} +3 -3
- 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-Yn9W3zp3.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-BAXYRVEO.js → loader-circle-De-7qQ2u.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/manifest-2ef99f38.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-DTAcYxBt.js → pause-CFxEKL1u.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/root-BxUQigda.js +67 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-fKo7v0Zo.js → search-BdBb5aqc.js} +2 -2
- 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-BG4heKCG.js → terminal-CrplD4b1.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DtSmdtM4.js → triangle-alert-DqJ0j69l.js} +2 -2
- 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-BPmOG9bE.js +13 -0
- package/codeyam-cli/src/webserver/build/server/assets/index-Cd-ufawF.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/init-CzeBGOto.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-Dht7CKXY.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 +586 -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/codeyam-preload.mjs +21 -3
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +130 -4
- package/codeyam-cli/src/webserver/server.js +100 -34
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +242 -48
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
- package/codeyam-cli/templates/chrome-extension-react/EXTENSION_SETUP.md +75 -0
- package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
- package/codeyam-cli/templates/chrome-extension-react/gitignore +15 -0
- package/codeyam-cli/templates/chrome-extension-react/index.html +12 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +27 -0
- package/codeyam-cli/templates/chrome-extension-react/popup.html +12 -0
- package/codeyam-cli/templates/chrome-extension-react/public/manifest.json +15 -0
- package/codeyam-cli/templates/chrome-extension-react/src/background/service-worker.ts +7 -0
- package/codeyam-cli/templates/chrome-extension-react/src/globals.css +6 -0
- package/codeyam-cli/templates/chrome-extension-react/src/lib/storage.ts +37 -0
- package/codeyam-cli/templates/chrome-extension-react/src/popup/App.tsx +12 -0
- package/codeyam-cli/templates/chrome-extension-react/src/popup/main.tsx +10 -0
- package/codeyam-cli/templates/chrome-extension-react/tsconfig.json +24 -0
- package/codeyam-cli/templates/chrome-extension-react/vite.config.ts +41 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +86 -5
- package/codeyam-cli/templates/codeyam-editor-reference.md +214 -0
- package/codeyam-cli/templates/editor-step-hook.py +193 -54
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +89 -0
- package/codeyam-cli/templates/expo-react-native/README.md +41 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +33 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +12 -0
- package/codeyam-cli/templates/expo-react-native/app.json +18 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +9 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +12 -0
- package/codeyam-cli/templates/expo-react-native/global.css +3 -0
- package/codeyam-cli/templates/expo-react-native/lib/storage.ts +32 -0
- package/codeyam-cli/templates/expo-react-native/metro.config.js +6 -0
- package/codeyam-cli/templates/expo-react-native/nativewind-env.d.ts +1 -0
- package/codeyam-cli/templates/expo-react-native/package.json +38 -0
- package/codeyam-cli/templates/expo-react-native/tailwind.config.js +10 -0
- package/codeyam-cli/templates/expo-react-native/tsconfig.json +10 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_PATTERNS.md +308 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/AUTH_UPGRADE.md +304 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +126 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/FEATURE_PATTERNS.md +37 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/app/lib/prisma.ts +9 -4
- package/codeyam-cli/templates/nextjs-prisma-sqlite/env +4 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +2 -1
- package/codeyam-cli/templates/nextjs-prisma-sqlite/prisma/seed.ts +4 -1
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +127 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
- package/codeyam-cli/templates/{nextjs-prisma-sqlite/PRISMA_SETUP.md → nextjs-prisma-supabase/SUPABASE_SETUP.md} +37 -17
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/api/todos/route.ts +17 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/globals.css +26 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/layout.tsx +34 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/prisma.ts +20 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/lib/supabase.ts +12 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/app/page.tsx +10 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/env +9 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/eslint.config.mjs +11 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/gitignore +40 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/next.config.ts +11 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +37 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/postcss.config.mjs +7 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/schema.prisma +27 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma/seed.ts +39 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/prisma.config.ts +12 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/tsconfig.json +34 -0
- package/codeyam-cli/templates/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 +90 -9
- package/package.json +2 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +101 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/database/src/lib/loadEntities.js +0 -6
- package/packages/database/src/lib/loadEntities.js.map +1 -1
- package/packages/database/src/lib/updateCommitMetadata.js +0 -25
- package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/packages/types/src/enums/ProjectFramework.js +2 -0
- package/packages/types/src/enums/ProjectFramework.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-DmJveP3T.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/createLucideIcon-CMT1jU2q.js +0 -21
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CltMNppm.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-Rfq_y0VR.js +0 -10
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-GNwaLSmC.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-CCa2trIL.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-DO4CZ16O.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/git-CdN8sCqs.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-Bd0cs8vw.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-9ab0aba3.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-Dg0mvYrI.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/root-3ciuWk-c.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/settings-DfuTtcJP.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/simulations-B3aOzpCZ.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-DCxIbVvl.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-E-peu3XZ.js +0 -367
|
@@ -11,28 +11,51 @@ import { IS_INTERNAL_BUILD } from "../utils/buildFlags.js";
|
|
|
11
11
|
import { startBackgroundServer } from "../utils/backgroundServer.js";
|
|
12
12
|
import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
|
-
import {
|
|
14
|
+
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
|
+
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
15
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
16
17
|
import initCommand from "./init.js";
|
|
17
|
-
import {
|
|
18
|
-
import { clearEditorState,
|
|
18
|
+
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
+
import { clearEditorState, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
|
+
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
19
22
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
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";
|
|
20
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
27
|
const __dirname = path.dirname(__filename);
|
|
22
28
|
const STEP_LABELS = {
|
|
23
29
|
1: 'Plan',
|
|
24
|
-
2: '
|
|
25
|
-
3: '
|
|
26
|
-
4: '
|
|
27
|
-
5: '
|
|
28
|
-
6: '
|
|
29
|
-
7: '
|
|
30
|
-
8: '
|
|
31
|
-
9: '
|
|
32
|
-
10: '
|
|
33
|
-
11: '
|
|
34
|
-
12: '
|
|
35
|
-
13: '
|
|
30
|
+
2: 'Prepare',
|
|
31
|
+
3: 'Prototype',
|
|
32
|
+
4: 'Verify Prototype',
|
|
33
|
+
5: 'Confirm',
|
|
34
|
+
6: 'Deconstruct',
|
|
35
|
+
7: 'Extract',
|
|
36
|
+
8: 'Glossary',
|
|
37
|
+
9: 'Analyze',
|
|
38
|
+
10: 'App Scenarios',
|
|
39
|
+
11: 'User Scenarios',
|
|
40
|
+
12: 'Verify',
|
|
41
|
+
13: 'Journal',
|
|
42
|
+
14: 'Review',
|
|
43
|
+
15: 'Present',
|
|
44
|
+
16: 'Commit',
|
|
45
|
+
17: 'Finalize',
|
|
46
|
+
18: 'Push',
|
|
47
|
+
};
|
|
48
|
+
const MIGRATION_STEP_LABELS = {
|
|
49
|
+
1: 'Survey',
|
|
50
|
+
2: 'App Scenarios',
|
|
51
|
+
3: 'Component Scenarios',
|
|
52
|
+
4: 'Preview',
|
|
53
|
+
5: 'Discuss',
|
|
54
|
+
6: 'Decompose',
|
|
55
|
+
7: 'Extract',
|
|
56
|
+
8: 'Recapture',
|
|
57
|
+
9: 'Journal',
|
|
58
|
+
10: 'Present',
|
|
36
59
|
};
|
|
37
60
|
/**
|
|
38
61
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
@@ -53,6 +76,18 @@ function logEvent(root, event, data) {
|
|
|
53
76
|
// Logging is best-effort
|
|
54
77
|
}
|
|
55
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Read the design system file if it exists.
|
|
81
|
+
*/
|
|
82
|
+
function readDesignSystem(root) {
|
|
83
|
+
const designSystemPath = path.join(root, '.codeyam', 'design-system.md');
|
|
84
|
+
try {
|
|
85
|
+
return fs.readFileSync(designSystemPath, 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
56
91
|
/**
|
|
57
92
|
* Get the project root (where .codeyam/ lives) or cwd.
|
|
58
93
|
*/
|
|
@@ -89,8 +124,9 @@ function writeState(root, state) {
|
|
|
89
124
|
}
|
|
90
125
|
/**
|
|
91
126
|
* Clear the editor state (for starting a new feature).
|
|
92
|
-
* Does NOT clear the user prompt file —
|
|
93
|
-
*
|
|
127
|
+
* Does NOT clear the user prompt file — the prompt is cleared
|
|
128
|
+
* by the journal API after recording, and the hook captures
|
|
129
|
+
* a fresh prompt for the next feature on UserPromptSubmit.
|
|
94
130
|
*/
|
|
95
131
|
function clearState(root) {
|
|
96
132
|
clearEditorState(root);
|
|
@@ -102,11 +138,62 @@ function hasProject(root) {
|
|
|
102
138
|
return fs.existsSync(path.join(root, 'package.json'));
|
|
103
139
|
}
|
|
104
140
|
/**
|
|
105
|
-
* Get the CodeYam server port
|
|
141
|
+
* Get the CodeYam server port.
|
|
142
|
+
* Reads from the server state file first (actual running port), then falls
|
|
143
|
+
* back to CODEYAM_PORT env var, then to the default 3111.
|
|
144
|
+
* This is critical when the server auto-selected a different port because
|
|
145
|
+
* the default was occupied by another project's server.
|
|
106
146
|
*/
|
|
107
147
|
function getServerPort() {
|
|
148
|
+
try {
|
|
149
|
+
const root = getProjectRoot();
|
|
150
|
+
if (root) {
|
|
151
|
+
const statePath = path.join(root, '.codeyam', 'server.json');
|
|
152
|
+
if (fs.existsSync(statePath)) {
|
|
153
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
154
|
+
if (state.port)
|
|
155
|
+
return String(state.port);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Fall through to defaults
|
|
161
|
+
}
|
|
108
162
|
return process.env.CODEYAM_PORT || '3111';
|
|
109
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Read the project's default dimension name and available screen size names.
|
|
166
|
+
* Used by step instructions to show project-specific dimension examples
|
|
167
|
+
* instead of hardcoded "Desktop".
|
|
168
|
+
*/
|
|
169
|
+
function getProjectDimensions(root) {
|
|
170
|
+
try {
|
|
171
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
172
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
173
|
+
const defaultName = config.defaultScreenSize?.name ||
|
|
174
|
+
(config.screenSizes ? Object.keys(config.screenSizes)[0] : null) ||
|
|
175
|
+
'Desktop';
|
|
176
|
+
const names = config.screenSizes ? Object.keys(config.screenSizes) : [];
|
|
177
|
+
return { defaultName, names };
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return { defaultName: 'Desktop', names: [] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Print dimension guidance when the project has multiple screen sizes.
|
|
185
|
+
* Tells Claude to pick the right dimension for the content being previewed
|
|
186
|
+
* instead of blindly using the default for everything.
|
|
187
|
+
*/
|
|
188
|
+
function printDimensionGuidance(defaultName, names) {
|
|
189
|
+
if (names.length <= 1)
|
|
190
|
+
return;
|
|
191
|
+
// Find a non-default dimension to suggest as the "other" option
|
|
192
|
+
const otherName = names.find((n) => n !== defaultName) || names[1];
|
|
193
|
+
console.log(chalk.yellow(` IMPORTANT: Choose the dimension that matches what you're previewing. Available: ${names.map((n) => `"${n}"`).join(', ')}.`));
|
|
194
|
+
console.log(chalk.yellow(` Do NOT always use "${defaultName}". Full pages, standalone views, and desktop layouts should use "${otherName}".`));
|
|
195
|
+
console.log(chalk.yellow(` Think about the CONTENT — a full-page library view needs a large viewport, not a popup-sized one.`));
|
|
196
|
+
}
|
|
110
197
|
/**
|
|
111
198
|
* Print a checklist item.
|
|
112
199
|
* Inline backtick-wrapped text is highlighted in cyan for visibility.
|
|
@@ -116,6 +203,157 @@ function checkbox(text) {
|
|
|
116
203
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
117
204
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
118
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
208
|
+
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
209
|
+
*/
|
|
210
|
+
function printAppScenarioInstructions(pageName, route) {
|
|
211
|
+
const root = process.cwd();
|
|
212
|
+
const hasSeedAdapter = !!detectSeedAdapter(root);
|
|
213
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
214
|
+
console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
|
|
215
|
+
console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
|
|
216
|
+
console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
|
|
217
|
+
console.log();
|
|
218
|
+
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
219
|
+
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
220
|
+
console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
|
|
221
|
+
console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
|
|
222
|
+
console.log();
|
|
223
|
+
checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
|
|
224
|
+
console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
|
|
225
|
+
console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
|
|
226
|
+
if (pageName) {
|
|
227
|
+
console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
231
|
+
}
|
|
232
|
+
console.log();
|
|
233
|
+
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
234
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
235
|
+
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
236
|
+
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
237
|
+
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
238
|
+
console.log();
|
|
239
|
+
if (hasSeedAdapter) {
|
|
240
|
+
checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data — without it the page will be empty!'));
|
|
241
|
+
console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
|
|
242
|
+
console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
|
|
243
|
+
console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
|
|
244
|
+
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
248
|
+
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
249
|
+
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
250
|
+
}
|
|
251
|
+
checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
|
|
252
|
+
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
253
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
254
|
+
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
255
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
256
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
257
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Shared glossary instructions used by editor Step 6 and migration Step 8.
|
|
261
|
+
* Prints format, required fields, and example.
|
|
262
|
+
*/
|
|
263
|
+
function printGlossaryInstructions(feature) {
|
|
264
|
+
console.log(chalk.bold.yellow('IMPORTANT: glossary.json is a plain JSON array — NOT an object wrapper.'));
|
|
265
|
+
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
266
|
+
checkbox('Add an entry for each new function/component extracted');
|
|
267
|
+
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
268
|
+
checkbox('For each function with a test file, set `testFile` to the relative path');
|
|
269
|
+
console.log();
|
|
270
|
+
console.log(chalk.bold('File format:'));
|
|
271
|
+
console.log(chalk.dim(' ['));
|
|
272
|
+
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
273
|
+
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
274
|
+
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
275
|
+
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
276
|
+
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
277
|
+
console.log(chalk.dim(` "feature": "${feature || '...'}", "createdAt": "..." }`));
|
|
278
|
+
console.log(chalk.dim(' ]'));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Shared extraction plan instructions used by editor Step 4 and migration Step 6.
|
|
282
|
+
* Prints THE RULE, extractable categories, plan format requirements.
|
|
283
|
+
*/
|
|
284
|
+
function printExtractionPlanInstructions() {
|
|
285
|
+
console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
|
|
286
|
+
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
287
|
+
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
288
|
+
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
289
|
+
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(chalk.bold('Checklist:'));
|
|
292
|
+
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
293
|
+
checkbox('Read EVERY file used by this page/feature');
|
|
294
|
+
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
295
|
+
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
296
|
+
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
297
|
+
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
298
|
+
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
299
|
+
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
300
|
+
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
301
|
+
console.log();
|
|
302
|
+
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
303
|
+
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
304
|
+
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
305
|
+
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
306
|
+
console.log();
|
|
307
|
+
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
308
|
+
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
309
|
+
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
310
|
+
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
311
|
+
console.log();
|
|
312
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
316
|
+
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
317
|
+
*/
|
|
318
|
+
function printComponentCaptureInstructions() {
|
|
319
|
+
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
320
|
+
console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
|
|
321
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
322
|
+
checkbox('For each visual component:');
|
|
323
|
+
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
324
|
+
console.log(chalk.dim(' — Props/interface'));
|
|
325
|
+
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
326
|
+
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
327
|
+
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
328
|
+
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
329
|
+
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
330
|
+
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
331
|
+
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
332
|
+
console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
|
|
333
|
+
console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
|
|
334
|
+
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
335
|
+
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
336
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
337
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
338
|
+
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
339
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
340
|
+
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
341
|
+
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
342
|
+
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
343
|
+
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
344
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
345
|
+
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
346
|
+
console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
|
|
347
|
+
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
348
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
|
|
349
|
+
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
350
|
+
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
351
|
+
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
352
|
+
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
353
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
354
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
355
|
+
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
356
|
+
}
|
|
119
357
|
/**
|
|
120
358
|
* Print a section header.
|
|
121
359
|
*/
|
|
@@ -128,13 +366,13 @@ function stepHeader(step, title, feature) {
|
|
|
128
366
|
console.log();
|
|
129
367
|
}
|
|
130
368
|
/**
|
|
131
|
-
* Print a colored progress tracker showing all
|
|
369
|
+
* Print a colored progress tracker showing all 18 steps.
|
|
132
370
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
133
371
|
*/
|
|
134
372
|
function printProgressTracker(current) {
|
|
135
373
|
console.log();
|
|
136
|
-
console.log(chalk.dim('┌─────────────────────────────────────┐'));
|
|
137
|
-
for (let i = 1; i <=
|
|
374
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
375
|
+
for (let i = 1; i <= 18; i++) {
|
|
138
376
|
const label = STEP_LABELS[i];
|
|
139
377
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
140
378
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -172,13 +410,13 @@ function stopGate(current, opts) {
|
|
|
172
410
|
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
173
411
|
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
174
412
|
console.log();
|
|
175
|
-
if (current <
|
|
413
|
+
if (current < 18) {
|
|
176
414
|
console.log(chalk.green('When done, run: ') +
|
|
177
415
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
178
416
|
}
|
|
179
417
|
else {
|
|
180
418
|
console.log(chalk.green('Feature complete! Run: ') +
|
|
181
|
-
chalk.bold('codeyam editor
|
|
419
|
+
chalk.bold('codeyam editor steps') +
|
|
182
420
|
chalk.green(' to start the next feature'));
|
|
183
421
|
}
|
|
184
422
|
console.log();
|
|
@@ -194,28 +432,38 @@ function printResumptionHeader(step) {
|
|
|
194
432
|
'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
195
433
|
],
|
|
196
434
|
2: ['Check if project files already exist:\n `ls package.json src/`'],
|
|
197
|
-
3: ['
|
|
198
|
-
4: [
|
|
435
|
+
3: ['Re-check is safe — just re-run the step'],
|
|
436
|
+
4: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
437
|
+
5: ['This is a confirmation step — just re-present to user'],
|
|
438
|
+
6: [
|
|
199
439
|
'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
200
440
|
],
|
|
201
|
-
|
|
441
|
+
7: [
|
|
202
442
|
'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
|
|
203
443
|
],
|
|
204
|
-
|
|
444
|
+
8: [
|
|
205
445
|
'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
|
|
206
446
|
],
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
447
|
+
9: ['Check if isolation routes and registered scenarios already exist'],
|
|
448
|
+
10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
|
|
449
|
+
11: [
|
|
210
450
|
'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
|
|
211
451
|
],
|
|
212
|
-
|
|
213
|
-
|
|
452
|
+
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
453
|
+
13: [
|
|
214
454
|
'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
|
|
215
455
|
'If an entry exists, use PATCH to update it — do NOT create a new one',
|
|
216
456
|
],
|
|
217
|
-
|
|
218
|
-
|
|
457
|
+
14: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
458
|
+
15: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
459
|
+
16: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
460
|
+
17: [
|
|
461
|
+
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
462
|
+
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
463
|
+
],
|
|
464
|
+
18: [
|
|
465
|
+
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
466
|
+
],
|
|
219
467
|
};
|
|
220
468
|
const label = STEP_LABELS[step] || 'Unknown';
|
|
221
469
|
console.log(chalk.bold.yellow(`━━━ RESUMING Step ${step}: ${label} ━━━`));
|
|
@@ -320,7 +568,7 @@ function parseDebugTargets(target) {
|
|
|
320
568
|
}
|
|
321
569
|
if (entry.startsWith('step-')) {
|
|
322
570
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
323
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
571
|
+
if (!isNaN(num) && num >= 1 && num <= 18) {
|
|
324
572
|
valid.add(`step-${num}`);
|
|
325
573
|
continue;
|
|
326
574
|
}
|
|
@@ -404,17 +652,88 @@ function printSetup(root) {
|
|
|
404
652
|
console.log();
|
|
405
653
|
console.log("No project detected. Let's get started.");
|
|
406
654
|
console.log();
|
|
655
|
+
// ── Design System ────────────────────────────────────────────────
|
|
656
|
+
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
657
|
+
console.log(chalk.dim(' Ask: "Do you have a design system, brand guidelines, or style preferences you\'d like me to follow?"'));
|
|
658
|
+
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
659
|
+
console.log(chalk.yellow(' Option 1 label: "Yes, I\'ll paste my design system"'));
|
|
660
|
+
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
661
|
+
console.log(chalk.yellow(' Option 2 label: "No, use sensible defaults"'));
|
|
662
|
+
console.log(chalk.dim(' → Skip'));
|
|
663
|
+
console.log();
|
|
407
664
|
console.log(chalk.bold('Checklist:'));
|
|
408
665
|
checkbox('Read `.codeyam/editor-mode-context.md` for session state');
|
|
409
666
|
checkbox('Ask the user what they want to build');
|
|
410
667
|
console.log();
|
|
668
|
+
// ── App Format Selection ───────────────────────────────────────────
|
|
669
|
+
console.log(chalk.bold('App Format Selection:'));
|
|
670
|
+
console.log(chalk.dim(' After the user describes their project, ask which app formats they want to target.'));
|
|
671
|
+
console.log(chalk.dim(' Use AskUserQuestion with CHECKBOXES (multiple selections allowed):'));
|
|
672
|
+
console.log();
|
|
673
|
+
for (const format of APP_FORMATS) {
|
|
674
|
+
console.log(chalk.yellow(` [ ] ${format.label}`) +
|
|
675
|
+
chalk.dim(` — ${format.description}`));
|
|
676
|
+
}
|
|
677
|
+
console.log();
|
|
678
|
+
// ── Tech Stack Selection ───────────────────────────────────────────
|
|
679
|
+
console.log(chalk.bold('Tech Stack Selection:'));
|
|
680
|
+
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
681
|
+
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
682
|
+
console.log(chalk.dim(' Show ALL matching stacks even if there is only one. Mark the recommended option.'));
|
|
683
|
+
console.log(chalk.dim(' Use AskUserQuestion to let the user pick one.'));
|
|
684
|
+
console.log();
|
|
685
|
+
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
686
|
+
for (const stack of TECH_STACKS) {
|
|
687
|
+
const recommended = stack.recommended ? chalk.green(' (Recommended)') : '';
|
|
688
|
+
const formatLabels = stack.supportedFormats
|
|
689
|
+
.map((f) => APP_FORMATS.find((af) => af.id === f)?.label)
|
|
690
|
+
.join(', ');
|
|
691
|
+
console.log(chalk.bold.yellow(` ${stack.name}`) +
|
|
692
|
+
recommended +
|
|
693
|
+
chalk.dim(` [id: ${stack.id}]`));
|
|
694
|
+
console.log(chalk.dim(` ${stack.description}`));
|
|
695
|
+
console.log(chalk.dim(' Technologies:'));
|
|
696
|
+
for (const tech of stack.technologies) {
|
|
697
|
+
console.log(chalk.dim(` - ${tech}`));
|
|
698
|
+
}
|
|
699
|
+
console.log(chalk.dim(` Supports: ${formatLabels}`));
|
|
700
|
+
console.log();
|
|
701
|
+
}
|
|
702
|
+
console.log(chalk.dim(' If NO stacks match the selected formats, tell the user that support for that format is coming soon'));
|
|
703
|
+
console.log(chalk.dim(' and suggest they pick a supported format for now.'));
|
|
704
|
+
console.log();
|
|
705
|
+
// ── Default Screen Size Selection ──────────────────────────────────
|
|
706
|
+
console.log(chalk.bold('Default Screen Size:'));
|
|
707
|
+
console.log(chalk.dim(' After selecting a tech stack, ask the user to choose the default screen size for previews and captures.'));
|
|
708
|
+
console.log(chalk.dim(' Use AskUserQuestion with RADIO buttons (single selection):'));
|
|
709
|
+
console.log();
|
|
710
|
+
console.log(chalk.yellow(' ( ) Desktop') + chalk.dim(' — 1440 × 900'));
|
|
711
|
+
console.log(chalk.yellow(' ( ) Laptop') + chalk.dim(' — 1024 × 768'));
|
|
712
|
+
console.log(chalk.yellow(' ( ) Tablet') + chalk.dim(' — 768 × 1024'));
|
|
713
|
+
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
714
|
+
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
715
|
+
console.log();
|
|
716
|
+
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).'));
|
|
717
|
+
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
718
|
+
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}}'`));
|
|
719
|
+
console.log();
|
|
720
|
+
console.log(chalk.bold('Named Screen Sizes (for multi-resolution apps):'));
|
|
721
|
+
console.log(chalk.dim(' For mobile-responsive web apps or apps that need screenshots at multiple resolutions,'));
|
|
722
|
+
console.log(chalk.dim(' also save named screen sizes. Each scenario can reference a dimension name.'));
|
|
723
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
724
|
+
console.log(chalk.dim(' -d \'{"screenSizes":{"Desktop":{"width":1440,"height":900},"Mobile":{"width":375,"height":667}}}\''));
|
|
725
|
+
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.'));
|
|
726
|
+
console.log(chalk.dim(' NOTE: This REPLACES all screenSizes — always include every named size, not just the new one.'));
|
|
727
|
+
console.log();
|
|
411
728
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
412
729
|
console.log();
|
|
413
|
-
console.log(chalk.red('Ask the user what they want to build, then run:'));
|
|
730
|
+
console.log(chalk.red('Ask the user what they want to build, then ask about app formats, then present tech stacks, then choose a default screen size. Then run:'));
|
|
414
731
|
console.log();
|
|
415
732
|
console.log(chalk.green(' ') +
|
|
416
|
-
chalk.bold('codeyam editor 1 --feature "Feature Name" --prompt "the user\'s original message"'));
|
|
733
|
+
chalk.bold('codeyam editor 1 --feature "Feature Name" --app-formats "FORMAT_IDS" --tech-stack "STACK_ID" --prompt "the user\'s original message"'));
|
|
417
734
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what the user described.'));
|
|
735
|
+
console.log(chalk.dim(' Replace FORMAT_IDS with the comma-separated format IDs the user selected (e.g., "chrome-extension" or "mobile-responsive-web-app").'));
|
|
736
|
+
console.log(chalk.dim(' Replace STACK_ID with the tech stack id shown in [brackets] above (e.g., "nextjs-prisma-sqlite" or "chrome-extension-react").'));
|
|
418
737
|
console.log(chalk.dim(" Pass --prompt with the user's exact original request so it appears in the journal."));
|
|
419
738
|
console.log(chalk.dim(' Step 1 will guide you through planning and getting user confirmation before any code is written.'));
|
|
420
739
|
console.log();
|
|
@@ -422,6 +741,42 @@ function printSetup(root) {
|
|
|
422
741
|
// ─── Cycle overview (no args, has project) ────────────────────────────
|
|
423
742
|
function printCycleOverview(root, state) {
|
|
424
743
|
logEvent(root, 'overview', state ? { feature: state.feature, step: state.step } : {});
|
|
744
|
+
// Detect active migration (uses both editor-step.json and migration-state.json)
|
|
745
|
+
const migState = readMigrationState(root);
|
|
746
|
+
const resumeInfo = getMigrationResumeInfo(state
|
|
747
|
+
? { step: state.step, label: state.label, migration: state.migration }
|
|
748
|
+
: null, migState);
|
|
749
|
+
if (resumeInfo.mode === 'survey') {
|
|
750
|
+
console.log();
|
|
751
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
752
|
+
console.log();
|
|
753
|
+
console.log(chalk.yellow('Migration survey in progress — Claude needs to explore the project.'));
|
|
754
|
+
console.log();
|
|
755
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
756
|
+
console.log();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (resumeInfo.mode === 'page-in-progress') {
|
|
760
|
+
console.log();
|
|
761
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
762
|
+
console.log();
|
|
763
|
+
console.log(chalk.yellow(`Migration: Page ${resumeInfo.pageIndex + 1}/${resumeInfo.totalPages} (${resumeInfo.pageName})` +
|
|
764
|
+
(resumeInfo.step
|
|
765
|
+
? `, Step ${resumeInfo.step} (${resumeInfo.stepLabel})`
|
|
766
|
+
: '')));
|
|
767
|
+
console.log();
|
|
768
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
769
|
+
console.log();
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
if (resumeInfo.mode === 'complete') {
|
|
773
|
+
console.log();
|
|
774
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
775
|
+
console.log();
|
|
776
|
+
console.log(chalk.green('Migration complete! Start building features:'));
|
|
777
|
+
console.log();
|
|
778
|
+
// Fall through to normal cycle display
|
|
779
|
+
}
|
|
425
780
|
console.log();
|
|
426
781
|
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
427
782
|
console.log();
|
|
@@ -433,47 +788,60 @@ function printCycleOverview(root, state) {
|
|
|
433
788
|
console.log(chalk.dim('Or run ') +
|
|
434
789
|
chalk.bold('codeyam editor 1') +
|
|
435
790
|
chalk.dim(' to start a new feature'));
|
|
791
|
+
console.log();
|
|
792
|
+
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
793
|
+
chalk.bold('codeyam editor change'));
|
|
794
|
+
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
436
795
|
}
|
|
437
796
|
else {
|
|
438
|
-
console.log('Each feature follows
|
|
797
|
+
console.log('Each feature follows 18 steps. You MUST run each command in order:');
|
|
439
798
|
console.log();
|
|
440
|
-
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')}
|
|
441
|
-
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('
|
|
442
|
-
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('
|
|
443
|
-
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('
|
|
444
|
-
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('
|
|
445
|
-
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('
|
|
446
|
-
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('
|
|
447
|
-
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('
|
|
448
|
-
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('
|
|
449
|
-
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('
|
|
450
|
-
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('
|
|
451
|
-
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('
|
|
452
|
-
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('
|
|
799
|
+
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
800
|
+
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} — Set up project and dependencies`);
|
|
801
|
+
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
802
|
+
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} — Verify prototype works correctly`);
|
|
803
|
+
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} — Confirm prototype with user`);
|
|
804
|
+
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
|
|
805
|
+
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} — TDD extraction of functions + components`);
|
|
806
|
+
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} — Record functions in glossary`);
|
|
807
|
+
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} — Analyze and verify components`);
|
|
808
|
+
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
|
|
809
|
+
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
|
|
810
|
+
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
|
|
811
|
+
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
812
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
813
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
814
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
815
|
+
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
816
|
+
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
453
817
|
console.log();
|
|
454
818
|
console.log(chalk.green('Start now: ') + chalk.bold('codeyam editor 1'));
|
|
455
819
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
456
820
|
}
|
|
821
|
+
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
457
822
|
console.log();
|
|
458
823
|
}
|
|
459
824
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
460
|
-
function printStep1(root, feature, userPrompt) {
|
|
825
|
+
function printStep1(root, feature, options, userPrompt) {
|
|
826
|
+
const port = getServerPort();
|
|
461
827
|
const prevState = readState(root);
|
|
462
828
|
const isResuming = prevState?.step === 1;
|
|
463
829
|
if (!isResuming) {
|
|
464
830
|
clearState(root);
|
|
465
831
|
}
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
832
|
+
// Always persist state so step 2's validation sees that step 1 ran.
|
|
833
|
+
// The feature name may not be known yet (it's an output of planning) —
|
|
834
|
+
// use an empty string as placeholder; step 2 requires --feature anyway.
|
|
835
|
+
const now = new Date().toISOString();
|
|
836
|
+
writeState(root, {
|
|
837
|
+
feature: feature || prevState?.feature || '',
|
|
838
|
+
step: 1,
|
|
839
|
+
label: STEP_LABELS[1],
|
|
840
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
841
|
+
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
842
|
+
appFormats: options?.appFormats || prevState?.appFormats,
|
|
843
|
+
techStackId: options?.techStackId || prevState?.techStackId,
|
|
844
|
+
});
|
|
477
845
|
// Save the user's original prompt to a separate file
|
|
478
846
|
if (userPrompt) {
|
|
479
847
|
const promptPath = path.join(root, '.codeyam', 'editor-user-prompt.txt');
|
|
@@ -498,11 +866,24 @@ function printStep1(root, feature, userPrompt) {
|
|
|
498
866
|
console.log(chalk.dim(' Bad: Free-form text asking "What do you think about the data model?"'));
|
|
499
867
|
console.log(chalk.dim(' You can ask up to 4 questions at once. Bundle related questions into a single AskUserQuestion call.'));
|
|
500
868
|
checkbox("Summarize what you'll build in plain language the user can verify against their vision");
|
|
869
|
+
checkbox('Include a brief note on what interesting data states the scenarios should demonstrate');
|
|
870
|
+
console.log(chalk.dim(' Think: what seed data would put this feature through its paces? Diverse content, edge cases, empty vs rich.'));
|
|
871
|
+
checkbox('Set the project title and description for the App tab:');
|
|
872
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
873
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
874
|
+
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
501
875
|
console.log();
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
chalk.
|
|
505
|
-
|
|
876
|
+
const designSystem = readDesignSystem(root);
|
|
877
|
+
if (designSystem) {
|
|
878
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
879
|
+
console.log(chalk.dim(' Keep these design tokens in mind during planning.'));
|
|
880
|
+
console.log();
|
|
881
|
+
console.log(designSystem);
|
|
882
|
+
console.log();
|
|
883
|
+
}
|
|
884
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
885
|
+
console.log(chalk.green(' Option 1 label: "This plan is accurate, let\'s prototype it!"') + chalk.dim(' — proceed to step 2'));
|
|
886
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
506
887
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
507
888
|
console.log();
|
|
508
889
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
@@ -524,9 +905,10 @@ function printStep1(root, feature, userPrompt) {
|
|
|
524
905
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
525
906
|
console.log();
|
|
526
907
|
}
|
|
527
|
-
// ─── Step 2:
|
|
908
|
+
// ─── Step 2: Prepare ──────────────────────────────────────────────────
|
|
528
909
|
function printStep2(root, feature) {
|
|
529
910
|
const port = getServerPort();
|
|
911
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
530
912
|
const projectExists = hasProject(root);
|
|
531
913
|
const prevState = readState(root);
|
|
532
914
|
const isResuming = prevState?.step === 2;
|
|
@@ -537,13 +919,22 @@ function printStep2(root, feature) {
|
|
|
537
919
|
label: STEP_LABELS[2],
|
|
538
920
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
539
921
|
featureStartedAt: isResuming ? prevState.featureStartedAt : now,
|
|
922
|
+
appFormats: prevState?.appFormats,
|
|
923
|
+
techStackId: prevState?.techStackId,
|
|
540
924
|
});
|
|
541
|
-
logEvent(root, 'step', { step: 2, label: '
|
|
542
|
-
stepHeader(2, '
|
|
925
|
+
logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
|
|
926
|
+
stepHeader(2, 'Prepare', feature);
|
|
543
927
|
if (isResuming) {
|
|
544
928
|
printResumptionHeader(2);
|
|
545
929
|
}
|
|
546
|
-
console.log('
|
|
930
|
+
console.log(chalk.bold.red('━━━ MANDATORY: CREATE TASK NOW ━━━'));
|
|
931
|
+
console.log();
|
|
932
|
+
console.log(chalk.red(`Run TaskCreate with title: "Use \`codeyam editor\` to work on ${feature}"`));
|
|
933
|
+
console.log(chalk.red('DO NOT read files, scaffold, write code, or do ANYTHING else until this task exists.'));
|
|
934
|
+
console.log(chalk.red('This is not optional. Create the task, then continue below.'));
|
|
935
|
+
console.log(chalk.bold.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
936
|
+
console.log();
|
|
937
|
+
console.log('Get the project ready to build.');
|
|
547
938
|
console.log();
|
|
548
939
|
// If no project exists yet, include scaffolding instructions first
|
|
549
940
|
if (!projectExists) {
|
|
@@ -562,26 +953,126 @@ function printStep2(root, feature) {
|
|
|
562
953
|
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
563
954
|
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
564
955
|
console.log();
|
|
565
|
-
console.log(chalk.
|
|
956
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
957
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
958
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
959
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
960
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
961
|
+
console.log();
|
|
962
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
566
963
|
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
567
964
|
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
568
965
|
console.log();
|
|
569
|
-
console.log(chalk.bold('Build the feature:'));
|
|
570
966
|
}
|
|
967
|
+
else {
|
|
968
|
+
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
969
|
+
checkbox('List existing scenarios: `codeyam editor scenarios`');
|
|
970
|
+
console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
|
|
971
|
+
console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
|
|
972
|
+
checkbox('Pick the scenario with seed data most relevant to this feature');
|
|
973
|
+
console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
|
|
974
|
+
console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
|
|
975
|
+
checkbox('Load that scenario to seed the database and preview it:');
|
|
976
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
977
|
+
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
978
|
+
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
979
|
+
console.log();
|
|
980
|
+
}
|
|
981
|
+
stopGate(2);
|
|
982
|
+
}
|
|
983
|
+
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
984
|
+
function printStep3(root, feature) {
|
|
985
|
+
const port = getServerPort();
|
|
986
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
987
|
+
const projectExists = hasProject(root);
|
|
988
|
+
const prevState = readState(root);
|
|
989
|
+
const isResuming = prevState?.step === 3;
|
|
990
|
+
const now = new Date().toISOString();
|
|
991
|
+
writeState(root, {
|
|
992
|
+
feature,
|
|
993
|
+
step: 3,
|
|
994
|
+
label: STEP_LABELS[3],
|
|
995
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
996
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
997
|
+
appFormats: prevState?.appFormats,
|
|
998
|
+
techStackId: prevState?.techStackId,
|
|
999
|
+
});
|
|
1000
|
+
logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
|
|
1001
|
+
stepHeader(3, 'Prototype', feature);
|
|
1002
|
+
if (isResuming) {
|
|
1003
|
+
printResumptionHeader(3);
|
|
1004
|
+
}
|
|
1005
|
+
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1006
|
+
console.log();
|
|
571
1007
|
console.log(chalk.bold('Checklist:'));
|
|
572
1008
|
checkbox('Create API routes that read from the database via Prisma');
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
1009
|
+
if (!projectExists) {
|
|
1010
|
+
checkbox('Seed the database with demo data');
|
|
1011
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1012
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1013
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1014
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1015
|
+
}
|
|
578
1016
|
checkbox('Verify the dev server shows the changes');
|
|
1017
|
+
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
1018
|
+
// Responsive design guidance when building a mobile-responsive web app
|
|
1019
|
+
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1020
|
+
console.log();
|
|
1021
|
+
console.log(chalk.bold.magenta('Responsive Design (mobile-responsive-web-app):'));
|
|
1022
|
+
console.log(chalk.magenta(' This app must look great on BOTH desktop AND mobile. It is NOT a mobile-only app.'));
|
|
1023
|
+
console.log(chalk.magenta(' Design desktop-first with a full-width layout, then ensure it adapts gracefully to mobile.'));
|
|
1024
|
+
console.log(chalk.dim(' Use Tailwind responsive prefixes (sm:, md:, lg:) for layout shifts.'));
|
|
1025
|
+
console.log(chalk.dim(' Test at both Desktop (1280×800) and Mobile (390×844) sizes before presenting.'));
|
|
1026
|
+
}
|
|
1027
|
+
const designSystem = readDesignSystem(root);
|
|
1028
|
+
if (designSystem) {
|
|
1029
|
+
console.log();
|
|
1030
|
+
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
1031
|
+
console.log();
|
|
1032
|
+
console.log(designSystem);
|
|
1033
|
+
console.log();
|
|
1034
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1035
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1036
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1037
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1038
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1039
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1040
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1041
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1042
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1043
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1044
|
+
}
|
|
579
1045
|
console.log();
|
|
580
1046
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
581
1047
|
console.log(chalk.cyan(' The user is watching the preview. Refresh it after each meaningful change:'));
|
|
582
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
1048
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
583
1049
|
console.log(chalk.cyan(' Refresh after: first visible page, adding each UI section, seeding data, styling.'));
|
|
584
1050
|
console.log(chalk.cyan(' Aim for 4-8+ refreshes during prototyping — not one big reveal at the end.'));
|
|
1051
|
+
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1052
|
+
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1053
|
+
printDimensionGuidance(dim, dimNames);
|
|
1054
|
+
console.log();
|
|
1055
|
+
stopGate(3);
|
|
1056
|
+
}
|
|
1057
|
+
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
1058
|
+
function printStep4(root, feature) {
|
|
1059
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1060
|
+
const prevState = readState(root);
|
|
1061
|
+
const isResuming = prevState?.step === 4;
|
|
1062
|
+
const now = new Date().toISOString();
|
|
1063
|
+
writeState(root, {
|
|
1064
|
+
feature,
|
|
1065
|
+
step: 4,
|
|
1066
|
+
label: STEP_LABELS[4],
|
|
1067
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1068
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1069
|
+
});
|
|
1070
|
+
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1071
|
+
stepHeader(4, 'Verify Prototype', feature);
|
|
1072
|
+
if (isResuming) {
|
|
1073
|
+
printResumptionHeader(4);
|
|
1074
|
+
}
|
|
1075
|
+
console.log('Verify everything works before presenting the prototype.');
|
|
585
1076
|
console.log();
|
|
586
1077
|
console.log(chalk.bold('Verify the dev server:'));
|
|
587
1078
|
console.log(chalk.dim(` # Get dev server URL: codeyam editor dev-server`));
|
|
@@ -592,139 +1083,141 @@ function printStep2(root, feature) {
|
|
|
592
1083
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
593
1084
|
checkbox('Verify the page loads: curl the dev server URL and confirm HTTP 200 (not an error page)');
|
|
594
1085
|
checkbox('Verify API routes return valid JSON: curl each route and confirm no error responses');
|
|
595
|
-
checkbox('Check for broken images:
|
|
596
|
-
console.log(chalk.dim('
|
|
1086
|
+
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1087
|
+
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
1088
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
597
1089
|
console.log(chalk.dim(' Fix or replace any broken image URLs in the seed data before proceeding.'));
|
|
598
1090
|
checkbox('Check the dev server terminal output for runtime errors (missing modules, failed imports)');
|
|
1091
|
+
checkbox('Verify the live preview renders: `codeyam editor client-errors`');
|
|
1092
|
+
console.log(chalk.dim(' If `hasContent=false` or `liveErrors>0`, the preview is broken — fix the issue before proceeding.'));
|
|
1093
|
+
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
599
1094
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
600
1095
|
console.log();
|
|
1096
|
+
console.log(chalk.bold('Update README and setup script:'));
|
|
1097
|
+
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1098
|
+
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
1099
|
+
console.log(chalk.dim(' The README and setup script must stay accurate as you make changes.'));
|
|
1100
|
+
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1101
|
+
console.log();
|
|
601
1102
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
602
|
-
stopGate(
|
|
1103
|
+
stopGate(4);
|
|
603
1104
|
}
|
|
604
|
-
// ─── Step
|
|
605
|
-
function
|
|
1105
|
+
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
1106
|
+
function printStep5(root, feature) {
|
|
606
1107
|
const port = getServerPort();
|
|
1108
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
607
1109
|
const prevState = readState(root);
|
|
608
|
-
const isResuming = prevState?.step ===
|
|
1110
|
+
const isResuming = prevState?.step === 5;
|
|
609
1111
|
const now = new Date().toISOString();
|
|
610
1112
|
writeState(root, {
|
|
611
1113
|
feature,
|
|
612
|
-
step:
|
|
613
|
-
label: STEP_LABELS[
|
|
1114
|
+
step: 5,
|
|
1115
|
+
label: STEP_LABELS[5],
|
|
614
1116
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
615
1117
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
616
1118
|
});
|
|
617
|
-
logEvent(root, 'step', { step:
|
|
618
|
-
stepHeader(
|
|
1119
|
+
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1120
|
+
stepHeader(5, 'Confirm', feature);
|
|
619
1121
|
if (isResuming) {
|
|
620
|
-
printResumptionHeader(
|
|
1122
|
+
printResumptionHeader(5);
|
|
621
1123
|
}
|
|
622
1124
|
console.log('Summarize what was built and get user confirmation.');
|
|
623
1125
|
console.log();
|
|
624
1126
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
625
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\` — check the \`preview\` field for \`healthy: false\``);
|
|
1127
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
626
1128
|
checkbox('Verify API routes return valid data (curl each route)');
|
|
627
1129
|
console.log();
|
|
628
1130
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
629
|
-
checkbox('Run `codeyam editor verify-images \'{"paths":["/","
|
|
630
|
-
console.log(chalk.dim('
|
|
1131
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1132
|
+
console.log(chalk.dim(' Include ALL page paths and ALL image URLs from seed data / API responses.'));
|
|
1133
|
+
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images.'));
|
|
631
1134
|
checkbox('Fix or remove any image that returns non-200 before continuing');
|
|
1135
|
+
checkbox('Check for client-side errors: `codeyam editor client-errors`');
|
|
1136
|
+
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
1137
|
+
checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
|
|
1138
|
+
console.log();
|
|
1139
|
+
console.log(chalk.bold('Verify the captured user prompt:'));
|
|
1140
|
+
checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
|
|
1141
|
+
checkbox('If the file is missing or does not match what the user actually asked for, write the correct prompt text to `.codeyam/editor-user-prompt.txt`');
|
|
1142
|
+
console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
|
|
632
1143
|
console.log();
|
|
633
1144
|
console.log(chalk.bold('Then present to the user:'));
|
|
634
1145
|
checkbox('Summarize what was built (routes, components, data)');
|
|
635
|
-
checkbox(
|
|
1146
|
+
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
1147
|
+
printDimensionGuidance(dim, dimNames);
|
|
1148
|
+
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1149
|
+
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
636
1150
|
console.log();
|
|
637
|
-
console.log(chalk.bold('
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1151
|
+
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1152
|
+
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
1153
|
+
checkbox('Prompt the user to try key interactions: forms, navigation, buttons, etc.');
|
|
1154
|
+
checkbox('Ask the user: "Does everything work as expected?"');
|
|
1155
|
+
console.log();
|
|
1156
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1157
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
|
|
1158
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
1159
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
642
1160
|
console.log();
|
|
643
1161
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
644
|
-
stopGate(
|
|
1162
|
+
stopGate(5, { confirm: true });
|
|
645
1163
|
}
|
|
646
|
-
// ─── Step
|
|
647
|
-
function
|
|
1164
|
+
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
1165
|
+
function printStep6(root, feature) {
|
|
648
1166
|
const prevState = readState(root);
|
|
649
|
-
const isResuming = prevState?.step ===
|
|
1167
|
+
const isResuming = prevState?.step === 6;
|
|
650
1168
|
const now = new Date().toISOString();
|
|
651
1169
|
writeState(root, {
|
|
652
1170
|
feature,
|
|
653
|
-
step:
|
|
654
|
-
label: STEP_LABELS[
|
|
1171
|
+
step: 6,
|
|
1172
|
+
label: STEP_LABELS[6],
|
|
655
1173
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
656
1174
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
657
1175
|
});
|
|
658
|
-
logEvent(root, 'step', { step:
|
|
659
|
-
stepHeader(
|
|
1176
|
+
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1177
|
+
stepHeader(6, 'Deconstruct', feature);
|
|
660
1178
|
if (isResuming) {
|
|
661
|
-
printResumptionHeader(
|
|
1179
|
+
printResumptionHeader(6);
|
|
662
1180
|
}
|
|
663
1181
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
664
|
-
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step
|
|
665
|
-
console.log();
|
|
666
|
-
console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
|
|
667
|
-
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
668
|
-
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
669
|
-
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
670
|
-
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
1182
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
671
1183
|
console.log();
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
checkbox('Read EVERY file created or modified in this session');
|
|
675
|
-
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
676
|
-
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
677
|
-
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
678
|
-
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
679
|
-
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
680
|
-
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
681
|
-
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
682
|
-
console.log();
|
|
683
|
-
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
684
|
-
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
685
|
-
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
686
|
-
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
687
|
-
console.log();
|
|
688
|
-
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
689
|
-
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
690
|
-
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
691
|
-
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
692
|
-
console.log();
|
|
693
|
-
console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
|
|
694
|
-
stopGate(4);
|
|
1184
|
+
printExtractionPlanInstructions();
|
|
1185
|
+
stopGate(6);
|
|
695
1186
|
}
|
|
696
|
-
// ─── Step
|
|
697
|
-
function
|
|
1187
|
+
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
1188
|
+
function printStep7(root, feature) {
|
|
698
1189
|
const port = getServerPort();
|
|
1190
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
699
1191
|
const prevState = readState(root);
|
|
700
|
-
const isResuming = prevState?.step ===
|
|
1192
|
+
const isResuming = prevState?.step === 7;
|
|
701
1193
|
const now = new Date().toISOString();
|
|
702
1194
|
writeState(root, {
|
|
703
1195
|
feature,
|
|
704
|
-
step:
|
|
705
|
-
label: STEP_LABELS[
|
|
1196
|
+
step: 7,
|
|
1197
|
+
label: STEP_LABELS[7],
|
|
706
1198
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
707
1199
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
708
1200
|
});
|
|
709
|
-
logEvent(root, 'step', { step:
|
|
710
|
-
stepHeader(
|
|
1201
|
+
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1202
|
+
stepHeader(7, 'Extract', feature);
|
|
711
1203
|
if (isResuming) {
|
|
712
|
-
printResumptionHeader(
|
|
1204
|
+
printResumptionHeader(7);
|
|
713
1205
|
}
|
|
714
|
-
console.log('Execute your extraction plan from step
|
|
1206
|
+
console.log('Execute your extraction plan from step 6.');
|
|
715
1207
|
console.log();
|
|
716
1208
|
console.log(chalk.bold('Components:'));
|
|
717
1209
|
checkbox('Extract each component from your plan into its own file');
|
|
718
1210
|
checkbox('Page/route files must contain ZERO direct JSX — only imported components');
|
|
719
1211
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
720
|
-
console.log(chalk.dim(' No tests needed — visual verification happens in step
|
|
1212
|
+
console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
|
|
721
1213
|
console.log();
|
|
722
|
-
console.log(chalk.bold('Library functions (TDD):'));
|
|
723
|
-
checkbox('For each function: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1214
|
+
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
1215
|
+
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
724
1216
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
725
1217
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1218
|
+
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
726
1219
|
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
727
|
-
console.log(chalk.yellow(' Tests ARE the only coverage for library functions — step
|
|
1220
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
728
1221
|
console.log();
|
|
729
1222
|
console.log(chalk.bold('Recursive pass:'));
|
|
730
1223
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
@@ -736,218 +1229,208 @@ function printStep5(root, feature) {
|
|
|
736
1229
|
checkbox('Run all tests and verify they pass');
|
|
737
1230
|
checkbox('Page files contain ONLY imports + component composition — no raw HTML tags');
|
|
738
1231
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
739
|
-
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview\``);
|
|
1232
|
+
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1233
|
+
printDimensionGuidance(dim, dimNames);
|
|
740
1234
|
console.log(chalk.dim(' The user should see the preview stay healthy as you refactor — refresh periodically, not just at the end.'));
|
|
741
1235
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
742
1236
|
console.log();
|
|
743
1237
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
744
|
-
stopGate(
|
|
1238
|
+
stopGate(7);
|
|
745
1239
|
}
|
|
746
|
-
// ─── Step
|
|
747
|
-
function
|
|
1240
|
+
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
1241
|
+
function printStep8(root, feature) {
|
|
748
1242
|
const prevState = readState(root);
|
|
749
|
-
const isResuming = prevState?.step ===
|
|
1243
|
+
const isResuming = prevState?.step === 8;
|
|
750
1244
|
const now = new Date().toISOString();
|
|
751
1245
|
writeState(root, {
|
|
752
1246
|
feature,
|
|
753
|
-
step:
|
|
754
|
-
label: STEP_LABELS[
|
|
1247
|
+
step: 8,
|
|
1248
|
+
label: STEP_LABELS[8],
|
|
755
1249
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
756
1250
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
757
1251
|
});
|
|
758
|
-
logEvent(root, 'step', { step:
|
|
759
|
-
stepHeader(
|
|
1252
|
+
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1253
|
+
stepHeader(8, 'Glossary', feature);
|
|
760
1254
|
if (isResuming) {
|
|
761
|
-
printResumptionHeader(
|
|
1255
|
+
printResumptionHeader(8);
|
|
762
1256
|
}
|
|
763
1257
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
764
1258
|
console.log();
|
|
765
|
-
|
|
766
|
-
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
767
|
-
checkbox('Add an entry for each new function/component extracted in step 5');
|
|
768
|
-
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
769
|
-
checkbox('For each function with a test file from step 5, set `testFile` to the relative path');
|
|
770
|
-
console.log();
|
|
771
|
-
console.log(chalk.bold('Entry format:'));
|
|
772
|
-
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
773
|
-
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
774
|
-
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
775
|
-
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
776
|
-
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
777
|
-
console.log(chalk.dim(` "feature": "${feature}", "createdAt": "..." }`));
|
|
1259
|
+
printGlossaryInstructions(feature);
|
|
778
1260
|
console.log();
|
|
779
1261
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
780
|
-
stopGate(
|
|
1262
|
+
stopGate(8);
|
|
781
1263
|
}
|
|
782
|
-
// ─── Step
|
|
783
|
-
function
|
|
1264
|
+
// ─── Step 9: Analyze ──────────────────────────────────────────────────
|
|
1265
|
+
function printStep9(root, feature) {
|
|
784
1266
|
const port = getServerPort();
|
|
1267
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
785
1268
|
const prevState = readState(root);
|
|
786
|
-
const isResuming = prevState?.step ===
|
|
1269
|
+
const isResuming = prevState?.step === 9;
|
|
787
1270
|
const now = new Date().toISOString();
|
|
788
1271
|
writeState(root, {
|
|
789
1272
|
feature,
|
|
790
|
-
step:
|
|
791
|
-
label: STEP_LABELS[
|
|
1273
|
+
step: 9,
|
|
1274
|
+
label: STEP_LABELS[9],
|
|
792
1275
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
793
1276
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
794
1277
|
});
|
|
795
|
-
logEvent(root, 'step', { step:
|
|
796
|
-
stepHeader(
|
|
1278
|
+
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1279
|
+
stepHeader(9, 'Analyze and Verify', feature);
|
|
797
1280
|
if (isResuming) {
|
|
798
|
-
printResumptionHeader(
|
|
1281
|
+
printResumptionHeader(9);
|
|
799
1282
|
}
|
|
800
1283
|
console.log('Verify visual components (via isolation routes) and library functions (via tests).');
|
|
801
1284
|
console.log();
|
|
802
1285
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
803
|
-
checkbox('List all files with new/modified visual components from step
|
|
804
|
-
checkbox('Ensure `app/codeyam-isolate/layout.tsx` exists with production guard (create if missing)');
|
|
805
|
-
console.log(chalk.dim(' The layout returns notFound() in production, so isolation routes are safe to commit.'));
|
|
806
|
-
console.log(chalk.dim(' Content: import { notFound } from "next/navigation"; export default ... if (NODE_ENV === "production") notFound();'));
|
|
1286
|
+
checkbox('List all files with new/modified visual components from step 7');
|
|
807
1287
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
808
1288
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
809
1289
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
810
1290
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
811
|
-
|
|
812
|
-
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
813
|
-
console.log(chalk.dim(' — Props/interface'));
|
|
814
|
-
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
815
|
-
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
816
|
-
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
817
|
-
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
818
|
-
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
819
|
-
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
820
|
-
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
821
|
-
console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
|
|
822
|
-
console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
|
|
823
|
-
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
824
|
-
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
825
|
-
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
826
|
-
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
|
|
827
|
-
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
828
|
-
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
829
|
-
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
830
|
-
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
831
|
-
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
832
|
-
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
833
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
834
|
-
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
835
|
-
console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
|
|
836
|
-
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
837
|
-
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
838
|
-
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
839
|
-
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
840
|
-
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
841
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
842
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
843
|
-
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
1291
|
+
printComponentCaptureInstructions();
|
|
844
1292
|
console.log();
|
|
845
1293
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
846
|
-
checkbox('Run ALL test files created in step
|
|
1294
|
+
checkbox('Run ALL test files created in step 7');
|
|
847
1295
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
848
1296
|
checkbox('Verify every test passes');
|
|
849
1297
|
checkbox('If any test fails, fix the source code and re-run');
|
|
850
1298
|
console.log();
|
|
851
1299
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
852
1300
|
console.log();
|
|
853
|
-
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions have tests');
|
|
854
|
-
|
|
1301
|
+
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1302
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1303
|
+
console.log(chalk.dim(' When audit passes, the import graph is built automatically for change tracking.'));
|
|
1304
|
+
console.log();
|
|
1305
|
+
stopGate(9);
|
|
855
1306
|
}
|
|
856
|
-
// ─── Step
|
|
857
|
-
function
|
|
1307
|
+
// ─── Step 10: App Scenarios ────────────────────────────────────────────
|
|
1308
|
+
function printStep10(root, feature) {
|
|
858
1309
|
const port = getServerPort();
|
|
1310
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
859
1311
|
const prevState = readState(root);
|
|
860
|
-
const isResuming = prevState?.step ===
|
|
1312
|
+
const isResuming = prevState?.step === 10;
|
|
861
1313
|
const now = new Date().toISOString();
|
|
862
1314
|
writeState(root, {
|
|
863
1315
|
feature,
|
|
864
|
-
step:
|
|
865
|
-
label: STEP_LABELS[
|
|
1316
|
+
step: 10,
|
|
1317
|
+
label: STEP_LABELS[10],
|
|
866
1318
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
867
1319
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
868
1320
|
});
|
|
869
|
-
logEvent(root, 'step', { step:
|
|
870
|
-
stepHeader(
|
|
1321
|
+
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1322
|
+
stepHeader(10, 'App Scenarios', feature);
|
|
871
1323
|
if (isResuming) {
|
|
872
|
-
printResumptionHeader(
|
|
1324
|
+
printResumptionHeader(10);
|
|
873
1325
|
}
|
|
874
|
-
console.log('Create app-level scenarios
|
|
1326
|
+
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1327
|
+
console.log();
|
|
1328
|
+
console.log(chalk.bold('Goal: Every scenario should thoroughly exercise the feature with realistic data.'));
|
|
1329
|
+
console.log(chalk.dim(' Scenarios with minimal or generic data ("Test Item 1") won\'t reveal whether the feature works.'));
|
|
1330
|
+
console.log(chalk.dim(' Use rich, diverse seed data that puts the feature through its paces.'));
|
|
1331
|
+
console.log();
|
|
1332
|
+
console.log(chalk.bold.cyan('Make seed data work hard:'));
|
|
1333
|
+
console.log(chalk.cyan(' Every scenario — new or existing — should have data that EXERCISES the feature:'));
|
|
1334
|
+
console.log(chalk.cyan(' • Add data that uses new fields, relationships, or states the feature introduces'));
|
|
1335
|
+
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1336
|
+
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1337
|
+
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1338
|
+
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1339
|
+
console.log();
|
|
1340
|
+
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
1341
|
+
console.log(chalk.cyan(' • New pages or pages with no scenarios yet → create new scenarios'));
|
|
1342
|
+
console.log(chalk.cyan(' • Existing scenarios on affected pages → re-register with enhanced data'));
|
|
1343
|
+
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1344
|
+
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
1345
|
+
console.log();
|
|
1346
|
+
console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
|
|
1347
|
+
console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
|
|
1348
|
+
console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
|
|
1349
|
+
console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
|
|
1350
|
+
console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
|
|
875
1351
|
console.log();
|
|
876
|
-
console.log(chalk.bold('Checklist:'));
|
|
877
|
-
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
878
|
-
console.log(chalk.dim(' Review existing scenarios — reuse and update their mock data rather than'));
|
|
879
|
-
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
880
1352
|
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
881
|
-
console.log(chalk.dim(' If
|
|
1353
|
+
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
882
1354
|
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
883
1355
|
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
884
|
-
|
|
885
|
-
console.log(chalk.dim(' Each scenario provides data across ALL API routes for that state'));
|
|
886
|
-
console.log(chalk.dim(' Name app scenarios descriptively: "Empty", "Full Catalog", "API Error"'));
|
|
887
|
-
checkbox('Register each scenario (auto-captures screenshot):');
|
|
888
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
889
|
-
checkbox('After each registration, check the response for `clientErrors`');
|
|
890
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
891
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1356
|
+
printAppScenarioInstructions();
|
|
892
1357
|
console.log();
|
|
893
|
-
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step
|
|
894
|
-
|
|
1358
|
+
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
|
|
1359
|
+
console.log();
|
|
1360
|
+
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1361
|
+
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
1362
|
+
console.log(chalk.cyan(' • A "Library with Articles" screenshot showing an empty page means the scenario is BROKEN.'));
|
|
1363
|
+
console.log(chalk.cyan(' • Is the seed data diverse — different lengths, categories, counts — or just placeholders?'));
|
|
1364
|
+
console.log(chalk.cyan(' • Only create scenarios for states the CURRENT code can render.'));
|
|
1365
|
+
console.log();
|
|
1366
|
+
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1367
|
+
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1368
|
+
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1369
|
+
stopGate(10);
|
|
895
1370
|
}
|
|
896
|
-
// ─── Step
|
|
897
|
-
function
|
|
1371
|
+
// ─── Step 11: User Scenarios ───────────────────────────────────────────
|
|
1372
|
+
function printStep11(root, feature) {
|
|
898
1373
|
const port = getServerPort();
|
|
899
1374
|
const prevState = readState(root);
|
|
900
|
-
const isResuming = prevState?.step ===
|
|
1375
|
+
const isResuming = prevState?.step === 11;
|
|
901
1376
|
const now = new Date().toISOString();
|
|
902
1377
|
writeState(root, {
|
|
903
1378
|
feature,
|
|
904
|
-
step:
|
|
905
|
-
label: STEP_LABELS[
|
|
1379
|
+
step: 11,
|
|
1380
|
+
label: STEP_LABELS[11],
|
|
906
1381
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
907
1382
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
908
1383
|
});
|
|
909
|
-
logEvent(root, 'step', { step:
|
|
910
|
-
stepHeader(
|
|
1384
|
+
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1385
|
+
stepHeader(11, 'User Scenarios', feature);
|
|
911
1386
|
if (isResuming) {
|
|
912
|
-
printResumptionHeader(
|
|
1387
|
+
printResumptionHeader(11);
|
|
913
1388
|
}
|
|
914
|
-
console.log('Create per-persona
|
|
1389
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
|
|
915
1390
|
console.log();
|
|
916
|
-
console.log(chalk.bold('If the app has users:'));
|
|
917
|
-
|
|
918
|
-
console.log(
|
|
919
|
-
console.log(chalk.
|
|
920
|
-
|
|
921
|
-
console.log(chalk.
|
|
922
|
-
|
|
923
|
-
console.log(chalk.dim(
|
|
1391
|
+
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1392
|
+
console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
|
|
1393
|
+
console.log();
|
|
1394
|
+
console.log(chalk.bold('If the app has users/auth:'));
|
|
1395
|
+
console.log();
|
|
1396
|
+
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1397
|
+
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1398
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
|
|
1399
|
+
console.log();
|
|
1400
|
+
console.log(chalk.bold('Checklist:'));
|
|
1401
|
+
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1402
|
+
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1403
|
+
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1404
|
+
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1405
|
+
console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1406
|
+
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1407
|
+
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1408
|
+
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
1409
|
+
console.log(chalk.dim(' e.g. a mobile-first user persona should use "dimensions":["Mobile"] even if the base scenario is Desktop.'));
|
|
924
1410
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
925
1411
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
926
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
927
|
-
console.log();
|
|
928
|
-
console.log(chalk.bold('If the app has NO users:'));
|
|
929
|
-
console.log(chalk.dim(' Skip this step and proceed to step 10 (Verify).'));
|
|
930
1412
|
console.log();
|
|
931
|
-
console.log(chalk.dim('
|
|
932
|
-
stopGate(
|
|
1413
|
+
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1414
|
+
stopGate(11);
|
|
933
1415
|
}
|
|
934
|
-
// ─── Step
|
|
935
|
-
function
|
|
1416
|
+
// ─── Step 12: Verify ──────────────────────────────────────────────────
|
|
1417
|
+
function printStep12(root, feature) {
|
|
936
1418
|
const port = getServerPort();
|
|
1419
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
937
1420
|
const prevState = readState(root);
|
|
938
|
-
const isResuming = prevState?.step ===
|
|
1421
|
+
const isResuming = prevState?.step === 12;
|
|
939
1422
|
const now = new Date().toISOString();
|
|
940
1423
|
writeState(root, {
|
|
941
1424
|
feature,
|
|
942
|
-
step:
|
|
943
|
-
label: STEP_LABELS[
|
|
1425
|
+
step: 12,
|
|
1426
|
+
label: STEP_LABELS[12],
|
|
944
1427
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
945
1428
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
946
1429
|
});
|
|
947
|
-
logEvent(root, 'step', { step:
|
|
948
|
-
stepHeader(
|
|
1430
|
+
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1431
|
+
stepHeader(12, 'Verify', feature);
|
|
949
1432
|
if (isResuming) {
|
|
950
|
-
printResumptionHeader(
|
|
1433
|
+
printResumptionHeader(12);
|
|
951
1434
|
}
|
|
952
1435
|
console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
|
|
953
1436
|
console.log();
|
|
@@ -958,143 +1441,829 @@ function printStep10(root, feature) {
|
|
|
958
1441
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",...}'`));
|
|
959
1442
|
console.log();
|
|
960
1443
|
console.log(chalk.bold('Editor scenarios (App tab) — visual + error check:'));
|
|
961
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1444
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1445
|
+
printDimensionGuidance(dim, dimNames);
|
|
962
1446
|
checkbox('Click through each app-level and user-persona scenario in the preview');
|
|
1447
|
+
checkbox('For seed-based scenarios: verify data renders correctly after switching');
|
|
1448
|
+
console.log(chalk.dim(' Switch between scenarios and confirm the app reflects the seeded data each time'));
|
|
963
1449
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
964
1450
|
console.log(chalk.yellow(' If `hasErrors` is true: list each scenario with errors, fix the source code,'));
|
|
965
1451
|
console.log(chalk.yellow(' re-register the affected scenarios, and re-check until hasErrors is false'));
|
|
966
1452
|
console.log(chalk.dim(' Common errors: React errors, failed fetches, undefined references, hydration mismatches'));
|
|
967
|
-
checkbox('Verify no broken images:
|
|
1453
|
+
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
968
1454
|
console.log();
|
|
969
1455
|
console.log(chalk.bold('Library functions — test check:'));
|
|
970
|
-
checkbox('Re-run all test files from step
|
|
1456
|
+
checkbox('Re-run all test files from step 7 to confirm they still pass');
|
|
971
1457
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
972
1458
|
checkbox('If any test fails, fix the source code and re-run');
|
|
973
1459
|
console.log();
|
|
974
1460
|
console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
|
|
975
|
-
stopGate(
|
|
1461
|
+
stopGate(12);
|
|
1462
|
+
}
|
|
1463
|
+
// ─── Step 13: Journal ─────────────────────────────────────────────────
|
|
1464
|
+
function printStep13(root, feature) {
|
|
1465
|
+
const port = getServerPort();
|
|
1466
|
+
const prevState = readState(root);
|
|
1467
|
+
const isResuming = prevState?.step === 13;
|
|
1468
|
+
const now = new Date().toISOString();
|
|
1469
|
+
writeState(root, {
|
|
1470
|
+
feature,
|
|
1471
|
+
step: 13,
|
|
1472
|
+
label: STEP_LABELS[13],
|
|
1473
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1474
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1475
|
+
});
|
|
1476
|
+
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1477
|
+
stepHeader(13, 'Journal', feature);
|
|
1478
|
+
if (isResuming) {
|
|
1479
|
+
printResumptionHeader(13);
|
|
1480
|
+
}
|
|
1481
|
+
console.log('Create or update the journal entry for this feature.');
|
|
1482
|
+
console.log();
|
|
1483
|
+
console.log(chalk.bold('Checklist:'));
|
|
1484
|
+
checkbox('Write a concise description of what was built (2-3 sentences)');
|
|
1485
|
+
checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
|
|
1486
|
+
console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
1487
|
+
console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
|
|
1488
|
+
checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
|
|
1489
|
+
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1490
|
+
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1491
|
+
console.log();
|
|
1492
|
+
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
|
|
1493
|
+
stopGate(13);
|
|
1494
|
+
}
|
|
1495
|
+
// ─── Step 14: Review ──────────────────────────────────────────────────
|
|
1496
|
+
function printStep14(root, feature) {
|
|
1497
|
+
const port = getServerPort();
|
|
1498
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1499
|
+
const prevState = readState(root);
|
|
1500
|
+
const isResuming = prevState?.step === 14;
|
|
1501
|
+
const now = new Date().toISOString();
|
|
1502
|
+
writeState(root, {
|
|
1503
|
+
feature,
|
|
1504
|
+
step: 14,
|
|
1505
|
+
label: STEP_LABELS[14],
|
|
1506
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1507
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1508
|
+
});
|
|
1509
|
+
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
1510
|
+
stepHeader(14, 'Review', feature);
|
|
1511
|
+
if (isResuming) {
|
|
1512
|
+
printResumptionHeader(14);
|
|
1513
|
+
}
|
|
1514
|
+
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1515
|
+
console.log();
|
|
1516
|
+
console.log(chalk.bold('Checklist (do all of this silently):'));
|
|
1517
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1518
|
+
printDimensionGuidance(dim, dimNames);
|
|
1519
|
+
checkbox('Verify each component has screenshots in the App tab (grouped under Components)');
|
|
1520
|
+
checkbox('If any are missing, re-register them using `codeyam editor register`');
|
|
1521
|
+
checkbox(`Check for client errors: \`codeyam editor client-errors\``);
|
|
1522
|
+
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1523
|
+
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1524
|
+
checkbox('Fix or remove any broken images before continuing');
|
|
1525
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1526
|
+
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1527
|
+
checkbox('Do not proceed until all checks pass');
|
|
1528
|
+
stopGate(14);
|
|
1529
|
+
}
|
|
1530
|
+
// ─── Step 15: Present ─────────────────────────────────────────────────
|
|
1531
|
+
function printStep15(root, feature) {
|
|
1532
|
+
const prevState = readState(root);
|
|
1533
|
+
const isResuming = prevState?.step === 15;
|
|
1534
|
+
const now = new Date().toISOString();
|
|
1535
|
+
writeState(root, {
|
|
1536
|
+
feature,
|
|
1537
|
+
step: 15,
|
|
1538
|
+
label: STEP_LABELS[15],
|
|
1539
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1540
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1541
|
+
});
|
|
1542
|
+
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
1543
|
+
stepHeader(15, 'Present', feature);
|
|
1544
|
+
if (isResuming) {
|
|
1545
|
+
printResumptionHeader(15);
|
|
1546
|
+
}
|
|
1547
|
+
console.log('Present the results to the user and get their approval.');
|
|
1548
|
+
console.log();
|
|
1549
|
+
console.log(chalk.bold('Checklist:'));
|
|
1550
|
+
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1551
|
+
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1552
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1553
|
+
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1554
|
+
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1555
|
+
checkbox('Report test count and audit status (one line)');
|
|
1556
|
+
console.log();
|
|
1557
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1558
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1559
|
+
chalk.dim(' — git commit all changes and record in journal'));
|
|
1560
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1561
|
+
chalk.dim(' — describe changes, then re-verify'));
|
|
1562
|
+
console.log();
|
|
1563
|
+
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1564
|
+
checkbox('Advance to the commit step: `codeyam editor 16`');
|
|
1565
|
+
console.log();
|
|
1566
|
+
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1567
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
1568
|
+
checkbox('Ask what changes the user wants (if not already clear)');
|
|
1569
|
+
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1570
|
+
checkbox('THEN make the requested changes and follow the checklist');
|
|
1571
|
+
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1572
|
+
stopGate(15, { confirm: true });
|
|
1573
|
+
}
|
|
1574
|
+
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1575
|
+
/**
|
|
1576
|
+
* Print a progress tracker for the 8 migration steps.
|
|
1577
|
+
*/
|
|
1578
|
+
function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
|
|
1579
|
+
console.log();
|
|
1580
|
+
console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
|
|
1581
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
1582
|
+
for (let i = 1; i <= 10; i++) {
|
|
1583
|
+
const label = MIGRATION_STEP_LABELS[i];
|
|
1584
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
1585
|
+
const content = `${num}. ${label.padEnd(28)}`;
|
|
1586
|
+
if (i < current) {
|
|
1587
|
+
console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
|
|
1588
|
+
}
|
|
1589
|
+
else if (i === current) {
|
|
1590
|
+
console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
console.log(chalk.dim(` │ ○ ${content}│`));
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
console.log(chalk.dim(' └─────────────────────────────────────┘'));
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Print a STOP gate for migration steps.
|
|
1600
|
+
*/
|
|
1601
|
+
function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
|
|
1602
|
+
console.log();
|
|
1603
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
1604
|
+
console.log();
|
|
1605
|
+
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
1606
|
+
if (opts?.confirm) {
|
|
1607
|
+
console.log();
|
|
1608
|
+
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
1609
|
+
}
|
|
1610
|
+
console.log();
|
|
1611
|
+
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
1612
|
+
printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
|
|
1613
|
+
console.log();
|
|
1614
|
+
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
1615
|
+
console.log();
|
|
1616
|
+
if (current === 5) {
|
|
1617
|
+
// Step 5 (Discuss) can skip to step 9 if user declines decomposition
|
|
1618
|
+
console.log(chalk.green('If decomposing, run: ') +
|
|
1619
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1620
|
+
console.log(chalk.green('If skipping decomposition, run: ') +
|
|
1621
|
+
chalk.bold('codeyam editor migrate 9'));
|
|
1622
|
+
}
|
|
1623
|
+
else if (current < 10) {
|
|
1624
|
+
console.log(chalk.green('When done, run: ') +
|
|
1625
|
+
chalk.bold(`codeyam editor migrate ${current + 1}`));
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
console.log(chalk.green('Page complete! Run: ') +
|
|
1629
|
+
chalk.bold('codeyam editor migrate next') +
|
|
1630
|
+
chalk.green(' to start the next page'));
|
|
1631
|
+
}
|
|
1632
|
+
console.log();
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Write migration-aware editor state.
|
|
1636
|
+
*/
|
|
1637
|
+
function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
|
|
1638
|
+
const prevState = readState(root);
|
|
1639
|
+
const isResuming = prevState?.step === step && !!prevState?.migration;
|
|
1640
|
+
const now = new Date().toISOString();
|
|
1641
|
+
writeState(root, {
|
|
1642
|
+
feature: `Migration: ${pageName}`,
|
|
1643
|
+
step,
|
|
1644
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1645
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1646
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1647
|
+
migration: {
|
|
1648
|
+
pageName,
|
|
1649
|
+
pageIndex,
|
|
1650
|
+
totalPages,
|
|
1651
|
+
},
|
|
1652
|
+
});
|
|
1653
|
+
logEvent(root, 'step', {
|
|
1654
|
+
step,
|
|
1655
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1656
|
+
feature: `Migration: ${pageName}`,
|
|
1657
|
+
migration: true,
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Get the current migration page info from state, or return null.
|
|
1662
|
+
*/
|
|
1663
|
+
function getCurrentMigrationPage(root) {
|
|
1664
|
+
const migState = readMigrationState(root);
|
|
1665
|
+
if (!migState || migState.pages.length === 0)
|
|
1666
|
+
return null;
|
|
1667
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
1668
|
+
if (!page)
|
|
1669
|
+
return null;
|
|
1670
|
+
return {
|
|
1671
|
+
pageName: page.name,
|
|
1672
|
+
pageIndex: migState.currentPageIndex,
|
|
1673
|
+
totalPages: migState.pages.length,
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
// ─── Migration Step 1: Survey ───────────────────────────────────────
|
|
1677
|
+
function printMigrateStep1(root) {
|
|
1678
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1679
|
+
if (!pageInfo) {
|
|
1680
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1681
|
+
process.exit(1);
|
|
1682
|
+
}
|
|
1683
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1684
|
+
writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
|
|
1685
|
+
const migState = readMigrationState(root);
|
|
1686
|
+
const page = migState.pages[pageIndex];
|
|
1687
|
+
console.log();
|
|
1688
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1689
|
+
console.log();
|
|
1690
|
+
console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
|
|
1691
|
+
console.log();
|
|
1692
|
+
console.log(chalk.bold('Survey:'));
|
|
1693
|
+
checkbox(`Read the page file: \`${page.filePath}\``);
|
|
1694
|
+
checkbox('Follow every import — read all local components, hooks, utilities used by this page');
|
|
1695
|
+
checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
|
|
1696
|
+
checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
|
|
1697
|
+
checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
|
|
1698
|
+
checkbox('Note the page route and any dynamic segments');
|
|
1699
|
+
console.log();
|
|
1700
|
+
console.log(chalk.bold('Dev Server:'));
|
|
1701
|
+
checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
|
|
1702
|
+
checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
|
|
1703
|
+
console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
|
|
1704
|
+
console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
|
|
1705
|
+
checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
|
|
1706
|
+
checkbox("Verify it's running: check for successful startup output");
|
|
1707
|
+
console.log();
|
|
1708
|
+
migrationStopGate(1, pageName, pageIndex, totalPages);
|
|
1709
|
+
}
|
|
1710
|
+
// ─── Migration Step 2: App Scenarios ────────────────────────────────
|
|
1711
|
+
function printMigrateStep2(root) {
|
|
1712
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1713
|
+
if (!pageInfo) {
|
|
1714
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1715
|
+
process.exit(1);
|
|
1716
|
+
}
|
|
1717
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1718
|
+
writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
|
|
1719
|
+
const migState = readMigrationState(root);
|
|
1720
|
+
const page = migState.pages[pageIndex];
|
|
1721
|
+
console.log();
|
|
1722
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1723
|
+
console.log();
|
|
1724
|
+
console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
|
|
1725
|
+
console.log();
|
|
1726
|
+
console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
|
|
1727
|
+
console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
|
|
1728
|
+
console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
|
|
1729
|
+
console.log();
|
|
1730
|
+
console.log(chalk.bold('Seed Adapter Setup:'));
|
|
1731
|
+
checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
|
|
1732
|
+
console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
|
|
1733
|
+
console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
|
|
1734
|
+
console.log();
|
|
1735
|
+
console.log(chalk.dim(' How to create a seed adapter:'));
|
|
1736
|
+
console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
|
|
1737
|
+
console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
|
|
1738
|
+
console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
|
|
1739
|
+
console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
|
|
1740
|
+
console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
|
|
1741
|
+
console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
|
|
1742
|
+
console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
|
|
1743
|
+
console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
|
|
1744
|
+
console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
|
|
1745
|
+
console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
|
|
1746
|
+
console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
|
|
1747
|
+
console.log();
|
|
1748
|
+
printAppScenarioInstructions(pageName, page.route);
|
|
1749
|
+
console.log();
|
|
1750
|
+
migrationStopGate(2, pageName, pageIndex, totalPages);
|
|
1751
|
+
}
|
|
1752
|
+
// ─── Migration Step 3: Component Scenarios ──────────────────────────
|
|
1753
|
+
function printMigrateStep3(root) {
|
|
1754
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1755
|
+
if (!pageInfo) {
|
|
1756
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1757
|
+
process.exit(1);
|
|
1758
|
+
}
|
|
1759
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1760
|
+
writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
|
|
1761
|
+
console.log();
|
|
1762
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1763
|
+
console.log();
|
|
1764
|
+
console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
|
|
1765
|
+
console.log();
|
|
1766
|
+
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1767
|
+
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1768
|
+
console.log();
|
|
1769
|
+
printComponentCaptureInstructions();
|
|
1770
|
+
console.log();
|
|
1771
|
+
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1772
|
+
}
|
|
1773
|
+
// ─── Migration Step 4: Preview ──────────────────────────────────────
|
|
1774
|
+
function printMigrateStep4(root) {
|
|
1775
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1776
|
+
if (!pageInfo) {
|
|
1777
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1778
|
+
process.exit(1);
|
|
1779
|
+
}
|
|
1780
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1781
|
+
writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
|
|
1782
|
+
console.log();
|
|
1783
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1784
|
+
console.log();
|
|
1785
|
+
console.log(chalk.bold('Goal: Verify screenshots look correct and show results to the user.'));
|
|
1786
|
+
console.log();
|
|
1787
|
+
console.log(chalk.bold('Checklist:'));
|
|
1788
|
+
checkbox('Review all scenario screenshots for this page — do they look correct?');
|
|
1789
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1790
|
+
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1791
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1792
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1793
|
+
console.log();
|
|
1794
|
+
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1795
|
+
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
1796
|
+
}
|
|
1797
|
+
// ─── Migration Step 5: Discuss ──────────────────────────────────────
|
|
1798
|
+
function printMigrateStep5(root) {
|
|
1799
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1800
|
+
if (!pageInfo) {
|
|
1801
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1802
|
+
process.exit(1);
|
|
1803
|
+
}
|
|
1804
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1805
|
+
writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
|
|
1806
|
+
console.log();
|
|
1807
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1808
|
+
console.log();
|
|
1809
|
+
console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
|
|
1810
|
+
console.log();
|
|
1811
|
+
console.log(chalk.bold('Checklist:'));
|
|
1812
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1813
|
+
checkbox("Assess the page's complexity:");
|
|
1814
|
+
console.log(chalk.dim(' — How many inline components exist?'));
|
|
1815
|
+
console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
|
|
1816
|
+
console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
|
|
1817
|
+
console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
|
|
1818
|
+
checkbox('Present your assessment to the user');
|
|
1819
|
+
console.log();
|
|
1820
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1821
|
+
console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
|
|
1822
|
+
chalk.dim(' — continue to step 6 (Decompose)'));
|
|
1823
|
+
console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
|
|
1824
|
+
chalk.dim(' — skip to step 9 (Journal)'));
|
|
1825
|
+
console.log();
|
|
1826
|
+
console.log(chalk.bold.yellow('Routing:'));
|
|
1827
|
+
console.log(chalk.yellow(' If user chooses decomposition: ') +
|
|
1828
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1829
|
+
console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
|
|
1830
|
+
migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
|
|
1831
|
+
}
|
|
1832
|
+
// ─── Migration Step 6: Decompose ────────────────────────────────────
|
|
1833
|
+
function printMigrateStep6(root) {
|
|
1834
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1835
|
+
if (!pageInfo) {
|
|
1836
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1839
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1840
|
+
writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
|
|
1841
|
+
console.log();
|
|
1842
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1843
|
+
console.log();
|
|
1844
|
+
console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
|
|
1845
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1846
|
+
console.log();
|
|
1847
|
+
console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
|
|
1848
|
+
console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
|
|
1849
|
+
console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
|
|
1850
|
+
console.log();
|
|
1851
|
+
printExtractionPlanInstructions();
|
|
1852
|
+
migrationStopGate(6, pageName, pageIndex, totalPages);
|
|
1853
|
+
}
|
|
1854
|
+
// ─── Migration Step 7: Extract ──────────────────────────────────────
|
|
1855
|
+
function printMigrateStep7(root) {
|
|
1856
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1857
|
+
if (!pageInfo) {
|
|
1858
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1859
|
+
process.exit(1);
|
|
1860
|
+
}
|
|
1861
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1862
|
+
writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
|
|
1863
|
+
console.log();
|
|
1864
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1865
|
+
console.log();
|
|
1866
|
+
console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
|
|
1867
|
+
console.log();
|
|
1868
|
+
console.log(chalk.bold('Checklist:'));
|
|
1869
|
+
checkbox('Extract visual components (no tests needed for components)');
|
|
1870
|
+
checkbox('Extract library functions via TDD (write failing test first, then implement)');
|
|
1871
|
+
checkbox('Extract hooks as needed');
|
|
1872
|
+
checkbox('Recursive pass: check each extracted component for further extraction');
|
|
1873
|
+
console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
|
|
1874
|
+
checkbox('Verify the page file is ONLY imports + component composition');
|
|
1875
|
+
checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1876
|
+
console.log();
|
|
1877
|
+
console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
|
|
1878
|
+
migrationStopGate(7, pageName, pageIndex, totalPages);
|
|
1879
|
+
}
|
|
1880
|
+
// ─── Migration Step 8: Recapture ────────────────────────────────────
|
|
1881
|
+
function printMigrateStep8(root) {
|
|
1882
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1883
|
+
if (!pageInfo) {
|
|
1884
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1885
|
+
process.exit(1);
|
|
1886
|
+
}
|
|
1887
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1888
|
+
writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
|
|
1889
|
+
const migState = readMigrationState(root);
|
|
1890
|
+
const page = migState.pages[pageIndex];
|
|
1891
|
+
console.log();
|
|
1892
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1893
|
+
console.log();
|
|
1894
|
+
console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
|
|
1895
|
+
console.log();
|
|
1896
|
+
console.log(chalk.bold('Glossary:'));
|
|
1897
|
+
printGlossaryInstructions();
|
|
1898
|
+
console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
|
|
1899
|
+
checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
|
|
1900
|
+
console.log();
|
|
1901
|
+
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1902
|
+
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1903
|
+
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1904
|
+
checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
|
|
1905
|
+
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1906
|
+
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1907
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
1908
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1909
|
+
console.log();
|
|
1910
|
+
console.log(chalk.bold('Component Scenarios:'));
|
|
1911
|
+
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1912
|
+
printComponentCaptureInstructions();
|
|
1913
|
+
console.log();
|
|
1914
|
+
console.log(chalk.bold('Verify:'));
|
|
1915
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
1916
|
+
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1917
|
+
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1918
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1919
|
+
checkbox('Fix any issues before proceeding');
|
|
1920
|
+
console.log();
|
|
1921
|
+
migrationStopGate(8, pageName, pageIndex, totalPages);
|
|
1922
|
+
}
|
|
1923
|
+
// ─── Migration Step 9: Journal ──────────────────────────────────────
|
|
1924
|
+
function printMigrateStep9(root) {
|
|
1925
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1926
|
+
if (!pageInfo) {
|
|
1927
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1928
|
+
process.exit(1);
|
|
1929
|
+
}
|
|
1930
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1931
|
+
writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
|
|
1932
|
+
console.log();
|
|
1933
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1934
|
+
console.log();
|
|
1935
|
+
console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
|
|
1936
|
+
console.log();
|
|
1937
|
+
console.log(chalk.bold('Checklist:'));
|
|
1938
|
+
checkbox('Write a concise description of what was migrated (2-3 sentences)');
|
|
1939
|
+
checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
|
|
1940
|
+
console.log();
|
|
1941
|
+
migrationStopGate(9, pageName, pageIndex, totalPages);
|
|
1942
|
+
}
|
|
1943
|
+
// ─── Migration Step 10: Present ─────────────────────────────────────
|
|
1944
|
+
function printMigrateStep10(root) {
|
|
1945
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1946
|
+
if (!pageInfo) {
|
|
1947
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1948
|
+
process.exit(1);
|
|
1949
|
+
}
|
|
1950
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1951
|
+
writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
|
|
1952
|
+
console.log();
|
|
1953
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1954
|
+
console.log();
|
|
1955
|
+
console.log("Show results and commit this page's migration.");
|
|
1956
|
+
console.log();
|
|
1957
|
+
console.log(chalk.bold('Checklist:'));
|
|
1958
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1959
|
+
console.log(chalk.dim(' The preview automatically switches to the first app-level scenario.'));
|
|
1960
|
+
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1961
|
+
console.log();
|
|
1962
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1963
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1964
|
+
chalk.dim(" — commit this page's migration"));
|
|
1965
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1966
|
+
chalk.dim(' — make changes, then re-verify'));
|
|
1967
|
+
console.log();
|
|
1968
|
+
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1969
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1970
|
+
checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
|
|
1971
|
+
checkbox('Mark page complete: `codeyam editor migrate complete`');
|
|
1972
|
+
const remaining = pageInfo.totalPages - pageIndex - 1;
|
|
1973
|
+
if (remaining > 0) {
|
|
1974
|
+
checkbox('Advance to next page: `codeyam editor migrate next`');
|
|
1975
|
+
console.log();
|
|
1976
|
+
console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
|
|
1977
|
+
}
|
|
1978
|
+
else {
|
|
1979
|
+
console.log();
|
|
1980
|
+
console.log(chalk.bold.green('Migration complete! All pages processed.'));
|
|
1981
|
+
console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
|
|
1982
|
+
console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
|
|
1983
|
+
}
|
|
1984
|
+
console.log();
|
|
1985
|
+
console.log(chalk.bold('If the user chooses "Make changes":'));
|
|
1986
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1987
|
+
checkbox('Ask what changes the user wants');
|
|
1988
|
+
checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
|
|
1989
|
+
console.log();
|
|
1990
|
+
migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
|
|
1991
|
+
}
|
|
1992
|
+
// ─── Migration Survey ───────────────────────────────────────────────
|
|
1993
|
+
function printMigrateSurvey(root) {
|
|
1994
|
+
console.log();
|
|
1995
|
+
console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
|
|
1996
|
+
console.log();
|
|
1997
|
+
console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
|
|
1998
|
+
console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
|
|
1999
|
+
console.log();
|
|
2000
|
+
// Write editor state so hooks and printCycleOverview detect migration
|
|
2001
|
+
const now = new Date().toISOString();
|
|
2002
|
+
writeState(root, {
|
|
2003
|
+
feature: 'Migration: Survey',
|
|
2004
|
+
step: 0,
|
|
2005
|
+
label: 'Survey',
|
|
2006
|
+
startedAt: now,
|
|
2007
|
+
featureStartedAt: now,
|
|
2008
|
+
migration: {
|
|
2009
|
+
pageName: 'Survey',
|
|
2010
|
+
pageIndex: -1,
|
|
2011
|
+
totalPages: 0,
|
|
2012
|
+
},
|
|
2013
|
+
});
|
|
2014
|
+
logEvent(root, 'migration-survey', {});
|
|
2015
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
2016
|
+
console.log();
|
|
2017
|
+
console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
|
|
2018
|
+
console.log(chalk.green('After confirmation and writing migration-state.json: ') +
|
|
2019
|
+
chalk.bold('codeyam editor migrate 1'));
|
|
2020
|
+
console.log();
|
|
2021
|
+
}
|
|
2022
|
+
// ─── Migration Status ───────────────────────────────────────────────
|
|
2023
|
+
function printMigrationStatus(root) {
|
|
2024
|
+
const state = readMigrationState(root);
|
|
2025
|
+
if (!state) {
|
|
2026
|
+
console.log(chalk.dim('No migration in progress.'));
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
console.log();
|
|
2030
|
+
console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
|
|
2031
|
+
console.log();
|
|
2032
|
+
const done = state.pages.filter((p) => p.status === 'complete').length;
|
|
2033
|
+
const total = state.pages.length;
|
|
2034
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
2035
|
+
console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
|
|
2036
|
+
console.log();
|
|
2037
|
+
for (let i = 0; i < state.pages.length; i++) {
|
|
2038
|
+
const p = state.pages[i];
|
|
2039
|
+
let icon;
|
|
2040
|
+
let color;
|
|
2041
|
+
if (p.status === 'complete') {
|
|
2042
|
+
icon = '✓';
|
|
2043
|
+
color = chalk.green;
|
|
2044
|
+
}
|
|
2045
|
+
else if (p.status === 'in-progress') {
|
|
2046
|
+
icon = '→';
|
|
2047
|
+
color = chalk.bold.cyan;
|
|
2048
|
+
}
|
|
2049
|
+
else {
|
|
2050
|
+
icon = '○';
|
|
2051
|
+
color = chalk.dim;
|
|
2052
|
+
}
|
|
2053
|
+
const extra = p.status === 'complete'
|
|
2054
|
+
? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
|
|
2055
|
+
: '';
|
|
2056
|
+
console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
|
|
2057
|
+
}
|
|
2058
|
+
if (state.sharedComponents.length > 0) {
|
|
2059
|
+
console.log();
|
|
2060
|
+
console.log(chalk.bold('Shared Components:'));
|
|
2061
|
+
for (const sc of state.sharedComponents) {
|
|
2062
|
+
console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
console.log();
|
|
2066
|
+
}
|
|
2067
|
+
// ─── Migration Command Dispatcher ───────────────────────────────────
|
|
2068
|
+
function handleMigrateCommand(root, subArg) {
|
|
2069
|
+
// No subcommand or explicit empty → run initial survey
|
|
2070
|
+
if (!subArg) {
|
|
2071
|
+
const migState = readMigrationState(root);
|
|
2072
|
+
if (migState?.status === 'in-progress') {
|
|
2073
|
+
// Already in progress — show status and resume hint
|
|
2074
|
+
printMigrationStatus(root);
|
|
2075
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2076
|
+
if (page) {
|
|
2077
|
+
const editorState = readState(root);
|
|
2078
|
+
const currentStep = editorState?.migration ? editorState.step : 1;
|
|
2079
|
+
console.log(chalk.green('Continue with: ') +
|
|
2080
|
+
chalk.bold(`codeyam editor migrate ${currentStep}`));
|
|
2081
|
+
console.log();
|
|
2082
|
+
}
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (migState?.status === 'complete') {
|
|
2086
|
+
console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
printMigrateSurvey(root);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
// Parse subcommand
|
|
2093
|
+
if (subArg === 'next') {
|
|
2094
|
+
const result = advanceToNextPage(root);
|
|
2095
|
+
if (!result) {
|
|
2096
|
+
const migState = readMigrationState(root);
|
|
2097
|
+
if (migState?.status === 'complete') {
|
|
2098
|
+
console.log();
|
|
2099
|
+
console.log(chalk.bold.green('🎉 Migration complete!'));
|
|
2100
|
+
console.log();
|
|
2101
|
+
const done = migState.pages.filter((p) => p.status === 'complete');
|
|
2102
|
+
const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
|
|
2103
|
+
const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
|
|
2104
|
+
const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
|
|
2105
|
+
console.log(` Pages migrated: ${done.length}`);
|
|
2106
|
+
console.log(` Components extracted: ${totalComponents}`);
|
|
2107
|
+
console.log(` Functions extracted: ${totalFunctions}`);
|
|
2108
|
+
console.log(` Scenarios created: ${totalScenarios}`);
|
|
2109
|
+
console.log(` Shared components: ${migState.sharedComponents.length}`);
|
|
2110
|
+
console.log();
|
|
2111
|
+
console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
|
|
2112
|
+
console.log();
|
|
2113
|
+
}
|
|
2114
|
+
else {
|
|
2115
|
+
console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
|
|
2116
|
+
}
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
// Start M1 for the next page
|
|
2120
|
+
printMigrateStep1(root);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (subArg === 'status') {
|
|
2124
|
+
printMigrationStatus(root);
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (subArg === 'complete') {
|
|
2128
|
+
const migState = readMigrationState(root);
|
|
2129
|
+
if (!migState) {
|
|
2130
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
2131
|
+
process.exit(1);
|
|
2132
|
+
}
|
|
2133
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2134
|
+
if (!page) {
|
|
2135
|
+
console.error(chalk.red('Error: No active migration page.'));
|
|
2136
|
+
process.exit(1);
|
|
2137
|
+
}
|
|
2138
|
+
completePage(root, {
|
|
2139
|
+
extractedComponents: page.extractedComponents,
|
|
2140
|
+
extractedFunctions: page.extractedFunctions,
|
|
2141
|
+
scenarioCount: page.scenarioCount,
|
|
2142
|
+
});
|
|
2143
|
+
console.log(chalk.green(`Page "${page.name}" marked complete.`));
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
// Numeric step: 1-8
|
|
2147
|
+
const step = parseInt(subArg, 10);
|
|
2148
|
+
if (isNaN(step) || step < 1 || step > 10) {
|
|
2149
|
+
console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
|
|
2150
|
+
process.exit(1);
|
|
2151
|
+
}
|
|
2152
|
+
// Ensure migration is active and first page started
|
|
2153
|
+
const migState = readMigrationState(root);
|
|
2154
|
+
if (!migState) {
|
|
2155
|
+
console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
|
|
2156
|
+
process.exit(1);
|
|
2157
|
+
}
|
|
2158
|
+
// If still in survey state and step 1 requested, start first page
|
|
2159
|
+
if (migState.status === 'surveyed' && step === 1) {
|
|
2160
|
+
migState.status = 'in-progress';
|
|
2161
|
+
migState.pages[0].status = 'in-progress';
|
|
2162
|
+
migState.pages[0].startedAt = new Date().toISOString();
|
|
2163
|
+
writeMigrationState(root, migState);
|
|
2164
|
+
}
|
|
2165
|
+
const stepFns = {
|
|
2166
|
+
1: printMigrateStep1,
|
|
2167
|
+
2: printMigrateStep2,
|
|
2168
|
+
3: printMigrateStep3,
|
|
2169
|
+
4: printMigrateStep4,
|
|
2170
|
+
5: printMigrateStep5,
|
|
2171
|
+
6: printMigrateStep6,
|
|
2172
|
+
7: printMigrateStep7,
|
|
2173
|
+
8: printMigrateStep8,
|
|
2174
|
+
9: printMigrateStep9,
|
|
2175
|
+
10: printMigrateStep10,
|
|
2176
|
+
};
|
|
2177
|
+
stepFns[step](root);
|
|
976
2178
|
}
|
|
977
|
-
// ─── Step
|
|
978
|
-
function
|
|
979
|
-
const port = getServerPort();
|
|
2179
|
+
// ─── Step 16: Commit ─────────────────────────────────────────────────
|
|
2180
|
+
function printStep16(root, feature) {
|
|
980
2181
|
const prevState = readState(root);
|
|
981
|
-
const isResuming = prevState?.step ===
|
|
2182
|
+
const isResuming = prevState?.step === 16;
|
|
982
2183
|
const now = new Date().toISOString();
|
|
983
2184
|
writeState(root, {
|
|
984
2185
|
feature,
|
|
985
|
-
step:
|
|
986
|
-
label: STEP_LABELS[
|
|
2186
|
+
step: 16,
|
|
2187
|
+
label: STEP_LABELS[16],
|
|
987
2188
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
988
2189
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
989
2190
|
});
|
|
990
|
-
logEvent(root, 'step', { step:
|
|
991
|
-
stepHeader(
|
|
2191
|
+
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2192
|
+
stepHeader(16, 'Commit', feature);
|
|
992
2193
|
if (isResuming) {
|
|
993
|
-
printResumptionHeader(
|
|
2194
|
+
printResumptionHeader(16);
|
|
994
2195
|
}
|
|
995
|
-
console.log('
|
|
2196
|
+
console.log('Commit all changes for this feature.');
|
|
996
2197
|
console.log();
|
|
997
2198
|
console.log(chalk.bold('Checklist:'));
|
|
998
|
-
checkbox('
|
|
999
|
-
checkbox(`
|
|
1000
|
-
|
|
1001
|
-
console.log(chalk.dim('
|
|
1002
|
-
|
|
1003
|
-
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1004
|
-
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1005
|
-
console.log();
|
|
1006
|
-
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 13.'));
|
|
1007
|
-
stopGate(11);
|
|
2199
|
+
checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
|
|
2200
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
2201
|
+
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
2202
|
+
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
2203
|
+
stopGate(16);
|
|
1008
2204
|
}
|
|
1009
|
-
// ─── Step
|
|
1010
|
-
function
|
|
1011
|
-
const port = getServerPort();
|
|
2205
|
+
// ─── Step 17: Finalize ───────────────────────────────────────────────
|
|
2206
|
+
function printStep17(root, feature) {
|
|
1012
2207
|
const prevState = readState(root);
|
|
1013
|
-
const isResuming = prevState?.step ===
|
|
2208
|
+
const isResuming = prevState?.step === 17;
|
|
1014
2209
|
const now = new Date().toISOString();
|
|
1015
2210
|
writeState(root, {
|
|
1016
2211
|
feature,
|
|
1017
|
-
step:
|
|
1018
|
-
label: STEP_LABELS[
|
|
2212
|
+
step: 17,
|
|
2213
|
+
label: STEP_LABELS[17],
|
|
1019
2214
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1020
2215
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1021
2216
|
});
|
|
1022
|
-
logEvent(root, 'step', { step:
|
|
1023
|
-
stepHeader(
|
|
2217
|
+
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2218
|
+
stepHeader(17, 'Finalize', feature);
|
|
1024
2219
|
if (isResuming) {
|
|
1025
|
-
printResumptionHeader(
|
|
2220
|
+
printResumptionHeader(17);
|
|
1026
2221
|
}
|
|
1027
|
-
console.log('
|
|
2222
|
+
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
1028
2223
|
console.log();
|
|
1029
|
-
console.log(chalk.bold('Checklist
|
|
1030
|
-
checkbox(`
|
|
1031
|
-
checkbox('
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1035
|
-
checkbox('Run `codeyam editor verify-images \'{"paths":["/","/other-page"]}\'` with all page paths');
|
|
1036
|
-
checkbox('Fix or remove any broken images before continuing');
|
|
1037
|
-
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1038
|
-
checkbox('Do not proceed until all checks pass');
|
|
1039
|
-
stopGate(12);
|
|
2224
|
+
console.log(chalk.bold('Checklist:'));
|
|
2225
|
+
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
2226
|
+
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
2227
|
+
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
2228
|
+
stopGate(17);
|
|
1040
2229
|
}
|
|
1041
|
-
// ─── Step
|
|
1042
|
-
function
|
|
1043
|
-
const port = getServerPort();
|
|
2230
|
+
// ─── Step 18: Push ───────────────────────────────────────────────────
|
|
2231
|
+
function printStep18(root, feature) {
|
|
1044
2232
|
const prevState = readState(root);
|
|
1045
|
-
const isResuming = prevState?.step ===
|
|
2233
|
+
const isResuming = prevState?.step === 18;
|
|
1046
2234
|
const now = new Date().toISOString();
|
|
1047
2235
|
writeState(root, {
|
|
1048
2236
|
feature,
|
|
1049
|
-
step:
|
|
1050
|
-
label: STEP_LABELS[
|
|
2237
|
+
step: 18,
|
|
2238
|
+
label: STEP_LABELS[18],
|
|
1051
2239
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1052
2240
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1053
2241
|
});
|
|
1054
|
-
logEvent(root, 'step', { step:
|
|
1055
|
-
stepHeader(
|
|
2242
|
+
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2243
|
+
stepHeader(18, 'Push', feature);
|
|
1056
2244
|
if (isResuming) {
|
|
1057
|
-
printResumptionHeader(
|
|
2245
|
+
printResumptionHeader(18);
|
|
1058
2246
|
}
|
|
1059
|
-
console.log('
|
|
2247
|
+
console.log('Push the commit to the remote repository.');
|
|
1060
2248
|
console.log();
|
|
1061
2249
|
console.log(chalk.bold('Checklist:'));
|
|
1062
|
-
checkbox('
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
console.log(chalk.dim('
|
|
1066
|
-
console.log(chalk.dim('
|
|
1067
|
-
console.log(chalk.dim('
|
|
1068
|
-
checkbox('
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
console.log(chalk.dim(' This
|
|
1072
|
-
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1073
|
-
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1074
|
-
checkbox('Report test count and audit status (one line)');
|
|
1075
|
-
console.log();
|
|
1076
|
-
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion):'));
|
|
1077
|
-
console.log(chalk.green(' "Save & commit"') +
|
|
1078
|
-
chalk.dim(' — git commit all changes and record in journal'));
|
|
1079
|
-
console.log(chalk.yellow(' "I\'d like to make some changes"') +
|
|
1080
|
-
chalk.dim(' — describe changes, then re-verify'));
|
|
1081
|
-
console.log();
|
|
1082
|
-
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1083
|
-
checkbox(`Hide the results panel first: \`codeyam editor hide-results\``);
|
|
1084
|
-
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
1085
|
-
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
1086
|
-
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
1087
|
-
console.log(chalk.green(' Then run: ') +
|
|
1088
|
-
chalk.bold('codeyam editor steps') +
|
|
1089
|
-
chalk.green(' to start the next feature'));
|
|
2250
|
+
checkbox('Check if a git remote is configured: `git remote -v`');
|
|
2251
|
+
checkbox("Offer to push to remote (AskUserQuestion — STOP and wait for the user's answer before proceeding):");
|
|
2252
|
+
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"'));
|
|
2253
|
+
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2254
|
+
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"'));
|
|
2255
|
+
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2256
|
+
checkbox('Mark the current build task as complete');
|
|
2257
|
+
checkbox('Create a new task with EXACTLY this title: "Ask the user what to build next, then run `codeyam editor 1` to start the next feature"');
|
|
2258
|
+
console.log(chalk.dim(' There must ALWAYS be an active task. Never leave the task list empty.'));
|
|
2259
|
+
console.log(chalk.dim(' This task reminds you to use the editor workflow for the next feature.'));
|
|
1090
2260
|
console.log();
|
|
1091
|
-
console.log(chalk.bold('
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
stopGate(13, { confirm: true });
|
|
2261
|
+
console.log(chalk.bold.yellow('IMPORTANT: After this step, the current feature is DONE.'));
|
|
2262
|
+
console.log(chalk.yellow(' Any new user request — even if it sounds related — is a NEW feature.'));
|
|
2263
|
+
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2264
|
+
console.log(chalk.yellow(' The next feature MUST start with `codeyam editor 1`.'));
|
|
2265
|
+
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2266
|
+
stopGate(18, { confirm: true });
|
|
1098
2267
|
}
|
|
1099
2268
|
// ─── Command definition ───────────────────────────────────────────────
|
|
1100
2269
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
@@ -1104,37 +2273,78 @@ function printStep13(root, feature) {
|
|
|
1104
2273
|
* Runs data-structure-only analysis for all glossary entities, then outputs
|
|
1105
2274
|
* an import graph and entity data structures as JSON to stdout.
|
|
1106
2275
|
*/
|
|
1107
|
-
async function handleAnalyzeImports() {
|
|
2276
|
+
async function handleAnalyzeImports(options = {}) {
|
|
1108
2277
|
const root = getProjectRoot();
|
|
1109
2278
|
// Read glossary
|
|
1110
2279
|
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
1111
2280
|
if (!fs.existsSync(glossaryPath)) {
|
|
2281
|
+
if (options.silent) {
|
|
2282
|
+
// Internal caller — glossary doesn't exist yet, nothing to analyze
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
1112
2285
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
1113
|
-
console.error(chalk.dim(' Run codeyam editor
|
|
2286
|
+
console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
|
|
1114
2287
|
process.exit(1);
|
|
1115
2288
|
}
|
|
1116
2289
|
let glossaryEntries;
|
|
1117
2290
|
try {
|
|
1118
|
-
|
|
2291
|
+
const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2292
|
+
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
1119
2293
|
}
|
|
1120
2294
|
catch {
|
|
1121
2295
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
1122
2296
|
process.exit(1);
|
|
1123
2297
|
}
|
|
1124
|
-
if (
|
|
2298
|
+
if (glossaryEntries.length === 0) {
|
|
1125
2299
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
1126
2300
|
process.exit(1);
|
|
1127
2301
|
}
|
|
1128
2302
|
const filePaths = glossaryEntries.map((e) => e.filePath);
|
|
1129
|
-
|
|
2303
|
+
// Include page files so pages get analyzed as entities too.
|
|
2304
|
+
// This allows the import graph to map page names to their dependencies.
|
|
2305
|
+
// Use allFiles (not map values) to include ALL pages — multiple routes
|
|
2306
|
+
// can share a page name (e.g., /feedback/[id] and /feedback/new both
|
|
2307
|
+
// map to "Feedback") but each needs its own entity analysis.
|
|
2308
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
2309
|
+
const { allFiles: allPageFiles } = scanPageFilePaths(root);
|
|
2310
|
+
for (const pageFile of allPageFiles) {
|
|
2311
|
+
if (!filePaths.includes(pageFile)) {
|
|
2312
|
+
filePaths.push(pageFile);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
// Include file paths from editor scenarios (both component_path and
|
|
2316
|
+
// page_file_path) so that all components and pages with scenarios get
|
|
2317
|
+
// entities — critical for non-Next.js apps where scanPageFilePaths
|
|
2318
|
+
// doesn't find page.tsx files.
|
|
2319
|
+
try {
|
|
2320
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2321
|
+
const db = getDatabase();
|
|
2322
|
+
const scenarioFiles = await db
|
|
2323
|
+
.selectFrom('editor_scenarios')
|
|
2324
|
+
.select(['component_path', 'page_file_path'])
|
|
2325
|
+
.distinct()
|
|
2326
|
+
.execute();
|
|
2327
|
+
for (const row of scenarioFiles) {
|
|
2328
|
+
const r = row;
|
|
2329
|
+
for (const fp of [r.component_path, r.page_file_path]) {
|
|
2330
|
+
if (fp && !filePaths.includes(fp)) {
|
|
2331
|
+
filePaths.push(fp);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
catch {
|
|
2337
|
+
// Non-fatal — scenario file paths just won't be included
|
|
2338
|
+
}
|
|
1130
2339
|
const progress = new ProgressReporter();
|
|
1131
|
-
// Run data-structure-only analysis for all entities
|
|
1132
|
-
|
|
2340
|
+
// Run data-structure-only analysis for all entities (glossary + pages)
|
|
2341
|
+
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2342
|
+
// The analyzer will discover and create them from file paths.
|
|
2343
|
+
progress.start('Running import analysis for all entities...');
|
|
1133
2344
|
try {
|
|
1134
2345
|
await runAnalysisForEntities({
|
|
1135
2346
|
projectRoot: root,
|
|
1136
2347
|
filePaths,
|
|
1137
|
-
entityNames,
|
|
1138
2348
|
progress,
|
|
1139
2349
|
onlyDataStructure: true,
|
|
1140
2350
|
});
|
|
@@ -1151,8 +2361,11 @@ async function handleAnalyzeImports() {
|
|
|
1151
2361
|
await initializeEnvironment();
|
|
1152
2362
|
const entities = await loadEntities({});
|
|
1153
2363
|
if (!entities || entities.length === 0) {
|
|
1154
|
-
progress.
|
|
1155
|
-
|
|
2364
|
+
progress.succeed('No entities found in database yet — skipping import analysis. This is normal on the first feature.');
|
|
2365
|
+
if (!options.silent) {
|
|
2366
|
+
console.log(JSON.stringify({ imports: {}, entities: {} }));
|
|
2367
|
+
}
|
|
2368
|
+
return;
|
|
1156
2369
|
}
|
|
1157
2370
|
// Deduplicate to latest versions
|
|
1158
2371
|
const latestByKey = new Map();
|
|
@@ -1166,7 +2379,7 @@ async function handleAnalyzeImports() {
|
|
|
1166
2379
|
latestByKey.set(key, entity);
|
|
1167
2380
|
}
|
|
1168
2381
|
}
|
|
1169
|
-
const entityNameSet = new Set(
|
|
2382
|
+
const entityNameSet = new Set(glossaryEntries.map((e) => e.name));
|
|
1170
2383
|
const latestEntities = [...latestByKey.values()].filter((e) => entityNameSet.has(e.name));
|
|
1171
2384
|
// Build import graph from importedExports metadata
|
|
1172
2385
|
const imports = {};
|
|
@@ -1204,11 +2417,270 @@ async function handleAnalyzeImports() {
|
|
|
1204
2417
|
};
|
|
1205
2418
|
}
|
|
1206
2419
|
progress.succeed('Done');
|
|
1207
|
-
// Output combined JSON
|
|
1208
|
-
|
|
1209
|
-
|
|
2420
|
+
// Output combined JSON (suppressed when called internally during startup)
|
|
2421
|
+
if (!options.silent) {
|
|
2422
|
+
const summary = formatApiSubcommandResult('analyze-imports', {
|
|
2423
|
+
imports,
|
|
2424
|
+
entities: entityData,
|
|
2425
|
+
});
|
|
2426
|
+
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
2427
|
+
}
|
|
2428
|
+
// Backfill entity_sha on scenarios registered before entities existed.
|
|
2429
|
+
// This fixes the "missing data" UI state when scenarios were registered
|
|
2430
|
+
// while analyze-imports was still running in the background.
|
|
2431
|
+
try {
|
|
2432
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2433
|
+
const db = getDatabase();
|
|
2434
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
|
|
2435
|
+
sha: e.sha,
|
|
2436
|
+
name: e.name,
|
|
2437
|
+
filePath: e.filePath || '',
|
|
2438
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
2439
|
+
e.metadata?.namedExport === false,
|
|
2440
|
+
})));
|
|
2441
|
+
if (backfillResult.updated > 0 && !options.silent) {
|
|
2442
|
+
console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
catch {
|
|
2446
|
+
/* non-fatal */
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
2450
|
+
/**
|
|
2451
|
+
* Validate seed data structure and check for a seed adapter.
|
|
2452
|
+
* Usage: codeyam editor validate-seed '{"products":[...]}'
|
|
2453
|
+
*/
|
|
2454
|
+
function handleValidateSeed(jsonArg) {
|
|
2455
|
+
const root = process.cwd();
|
|
2456
|
+
// Check for seed adapter
|
|
2457
|
+
const adapterPath = detectSeedAdapter(root);
|
|
2458
|
+
if (adapterPath) {
|
|
2459
|
+
console.log(chalk.green(`Seed adapter found: ${adapterPath}`));
|
|
2460
|
+
}
|
|
2461
|
+
else {
|
|
2462
|
+
console.log(chalk.yellow('No seed adapter found. Create .codeyam/seed-adapter.ts to use seed-based scenarios.'));
|
|
2463
|
+
}
|
|
2464
|
+
if (!jsonArg) {
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
let data;
|
|
2468
|
+
try {
|
|
2469
|
+
data = JSON.parse(jsonArg);
|
|
2470
|
+
}
|
|
2471
|
+
catch {
|
|
2472
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
2473
|
+
process.exit(1);
|
|
2474
|
+
}
|
|
2475
|
+
const result = validateSeedData(data);
|
|
2476
|
+
if (result.valid) {
|
|
2477
|
+
const tables = Object.keys(data);
|
|
2478
|
+
const totalRows = tables.reduce((sum, t) => sum + (Array.isArray(data[t]) ? data[t].length : 0), 0);
|
|
2479
|
+
console.log(chalk.green(`Seed data valid: ${tables.length} table(s), ${totalRows} total row(s)`));
|
|
2480
|
+
for (const table of tables) {
|
|
2481
|
+
const rows = Array.isArray(data[table])
|
|
2482
|
+
? data[table].length
|
|
2483
|
+
: 0;
|
|
2484
|
+
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
2485
|
+
}
|
|
2486
|
+
// Check seed keys against Prisma schema if available
|
|
2487
|
+
const schemaPath = path.join(root, 'prisma', 'schema.prisma');
|
|
2488
|
+
try {
|
|
2489
|
+
if (fs.existsSync(schemaPath)) {
|
|
2490
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
2491
|
+
const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
|
|
2492
|
+
if (warnings.length > 0) {
|
|
2493
|
+
console.log();
|
|
2494
|
+
console.log(chalk.yellow('Prisma model name warnings:'));
|
|
2495
|
+
for (const w of warnings) {
|
|
2496
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
catch {
|
|
2502
|
+
// Schema not readable — skip Prisma validation
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
else {
|
|
2506
|
+
console.error(chalk.red('Seed data validation failed:'));
|
|
2507
|
+
for (const err of result.errors) {
|
|
2508
|
+
console.error(chalk.red(` - ${err}`));
|
|
2509
|
+
}
|
|
2510
|
+
process.exit(1);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
// ─── Delete subcommand ────────────────────────────────────────────────
|
|
2514
|
+
/**
|
|
2515
|
+
* `codeyam editor delete <scenarioId>`
|
|
2516
|
+
*
|
|
2517
|
+
* Delete a scenario by ID via the running editor server.
|
|
2518
|
+
*/
|
|
2519
|
+
async function handleDelete(scenarioId) {
|
|
2520
|
+
if (!scenarioId) {
|
|
2521
|
+
console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
|
|
2522
|
+
process.exit(1);
|
|
2523
|
+
}
|
|
2524
|
+
const port = getServerPort();
|
|
2525
|
+
const result = await deleteScenarioViaCli(scenarioId, port);
|
|
2526
|
+
if (result.success) {
|
|
2527
|
+
console.log(chalk.green(result.message));
|
|
2528
|
+
}
|
|
2529
|
+
else {
|
|
2530
|
+
console.error(chalk.red(result.message));
|
|
2531
|
+
process.exit(1);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
2535
|
+
/**
|
|
2536
|
+
* `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
|
|
2537
|
+
*
|
|
2538
|
+
* Creates isolation route directories and the layout guard file.
|
|
2539
|
+
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2540
|
+
*/
|
|
2541
|
+
function handleIsolate(componentNames) {
|
|
2542
|
+
const root = process.cwd();
|
|
2543
|
+
if (componentNames.length === 0) {
|
|
2544
|
+
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2545
|
+
process.exit(1);
|
|
2546
|
+
}
|
|
2547
|
+
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2548
|
+
// Create layout.tsx with production guard if missing
|
|
2549
|
+
const layoutPath = path.join(isolateDir, 'layout.tsx');
|
|
2550
|
+
if (!fs.existsSync(layoutPath)) {
|
|
2551
|
+
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2552
|
+
fs.writeFileSync(layoutPath, [
|
|
2553
|
+
'import { notFound } from "next/navigation";',
|
|
2554
|
+
'',
|
|
2555
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
2556
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
2557
|
+
' return <>{children}</>;',
|
|
2558
|
+
'}',
|
|
2559
|
+
'',
|
|
2560
|
+
].join('\n'), 'utf8');
|
|
2561
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/layout.tsx`));
|
|
2562
|
+
}
|
|
2563
|
+
// Create a directory for each component
|
|
2564
|
+
const created = [];
|
|
2565
|
+
const existed = [];
|
|
2566
|
+
for (const name of componentNames) {
|
|
2567
|
+
const dir = path.join(isolateDir, name);
|
|
2568
|
+
if (fs.existsSync(dir)) {
|
|
2569
|
+
existed.push(name);
|
|
2570
|
+
}
|
|
2571
|
+
else {
|
|
2572
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2573
|
+
created.push(name);
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
if (created.length > 0) {
|
|
2577
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
2578
|
+
}
|
|
2579
|
+
if (existed.length > 0) {
|
|
2580
|
+
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
2581
|
+
}
|
|
1210
2582
|
}
|
|
1211
2583
|
// ─── Register subcommand ──────────────────────────────────────────────
|
|
2584
|
+
/**
|
|
2585
|
+
* Format API subcommand results as concise one-line summaries.
|
|
2586
|
+
* Prevents Claude from piping output to python3 just to extract key fields.
|
|
2587
|
+
* Returns null for subcommands that should keep raw JSON output.
|
|
2588
|
+
*/
|
|
2589
|
+
function formatApiSubcommandResult(subcommand, data) {
|
|
2590
|
+
if (!data || typeof data !== 'object')
|
|
2591
|
+
return null;
|
|
2592
|
+
switch (subcommand) {
|
|
2593
|
+
case 'journal': {
|
|
2594
|
+
const parts = [`success=${data.success}`];
|
|
2595
|
+
if (data.entry?.time)
|
|
2596
|
+
parts.push(`time="${data.entry.time}"`);
|
|
2597
|
+
if (data.entry?.title)
|
|
2598
|
+
parts.push(`title="${data.entry.title}"`);
|
|
2599
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
2600
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
2601
|
+
}
|
|
2602
|
+
return parts.join(' ');
|
|
2603
|
+
}
|
|
2604
|
+
case 'journal-update': {
|
|
2605
|
+
const parts = [`success=${data.success}`];
|
|
2606
|
+
if (data.entry?.time)
|
|
2607
|
+
parts.push(`time="${data.entry.time}"`);
|
|
2608
|
+
if (data.scenarioScreenshotsFound != null) {
|
|
2609
|
+
parts.push(`screenshots=${data.scenarioScreenshotsFound}`);
|
|
2610
|
+
}
|
|
2611
|
+
return parts.join(' ');
|
|
2612
|
+
}
|
|
2613
|
+
case 'commit': {
|
|
2614
|
+
const parts = [`success=${data.success}`];
|
|
2615
|
+
if (data.sha)
|
|
2616
|
+
parts.push(`sha=${data.sha}`);
|
|
2617
|
+
if (data.message)
|
|
2618
|
+
parts.push(`message="${data.message.split('\n')[0]}"`);
|
|
2619
|
+
return parts.join(' ');
|
|
2620
|
+
}
|
|
2621
|
+
case 'preview': {
|
|
2622
|
+
const parts = [
|
|
2623
|
+
`success=${!!data.ok || data.success !== false}`,
|
|
2624
|
+
];
|
|
2625
|
+
if (data.path)
|
|
2626
|
+
parts.push(`path="${data.path}"`);
|
|
2627
|
+
if (data.scenarioId)
|
|
2628
|
+
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2629
|
+
if (data.sessionsNotified != null)
|
|
2630
|
+
parts.push(`notified=${data.sessionsNotified}`);
|
|
2631
|
+
return parts.join(' ');
|
|
2632
|
+
}
|
|
2633
|
+
case 'dev-server': {
|
|
2634
|
+
if (data.status) {
|
|
2635
|
+
const parts = [`status=${data.status}`, `pid=${data.pid || 'none'}`];
|
|
2636
|
+
if (data.url)
|
|
2637
|
+
parts.push(`url=${data.url}`);
|
|
2638
|
+
if (data.proxyUrl)
|
|
2639
|
+
parts.push(`proxyUrl=${data.proxyUrl}`);
|
|
2640
|
+
if (data.errorMessage)
|
|
2641
|
+
parts.push(`error: ${data.errorMessage}`);
|
|
2642
|
+
return parts.join(' ');
|
|
2643
|
+
}
|
|
2644
|
+
return null;
|
|
2645
|
+
}
|
|
2646
|
+
case 'client-errors': {
|
|
2647
|
+
const parts = [`hasErrors=${data.hasErrors}`];
|
|
2648
|
+
parts.push(`totalErrors=${data.totalErrors ?? 0}`);
|
|
2649
|
+
if (data.livePreview) {
|
|
2650
|
+
const lp = data.livePreview;
|
|
2651
|
+
parts.push(`livePreview=${lp.loaded ? 'loaded' : 'not-loaded'}`);
|
|
2652
|
+
if (lp.loaded) {
|
|
2653
|
+
parts.push(`hasContent=${lp.hasContent}`);
|
|
2654
|
+
}
|
|
2655
|
+
const liveErrors = lp.errors?.length ?? 0;
|
|
2656
|
+
parts.push(`liveErrors=${liveErrors}`);
|
|
2657
|
+
if (liveErrors > 0) {
|
|
2658
|
+
// Show first few error messages for context
|
|
2659
|
+
for (const err of lp.errors.slice(0, 3)) {
|
|
2660
|
+
parts.push(` error: ${err.message}`);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
else {
|
|
2665
|
+
parts.push('livePreview=not-loaded');
|
|
2666
|
+
}
|
|
2667
|
+
return parts.join(' ');
|
|
2668
|
+
}
|
|
2669
|
+
case 'analyze-imports': {
|
|
2670
|
+
const importEntries = Object.entries(data.imports || {});
|
|
2671
|
+
const entityEntries = Object.entries(data.entities || {});
|
|
2672
|
+
const parts = [];
|
|
2673
|
+
parts.push(`entities=${entityEntries.length}`);
|
|
2674
|
+
parts.push(`withImports=${importEntries.length}`);
|
|
2675
|
+
if (importEntries.length > 0) {
|
|
2676
|
+
parts.push(`imports: ${importEntries.map(([name, deps]) => `${name}→[${deps.join(',')}]`).join(' ')}`);
|
|
2677
|
+
}
|
|
2678
|
+
return parts.join(' ');
|
|
2679
|
+
}
|
|
2680
|
+
default:
|
|
2681
|
+
return null; // journal-list, show/hide-results: keep full JSON
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
1212
2684
|
/**
|
|
1213
2685
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
1214
2686
|
*
|
|
@@ -1217,40 +2689,158 @@ async function handleAnalyzeImports() {
|
|
|
1217
2689
|
*/
|
|
1218
2690
|
async function handleRegister(jsonArg) {
|
|
1219
2691
|
if (!jsonArg) {
|
|
2692
|
+
const { defaultName: dim } = getProjectDimensions(getProjectRoot());
|
|
1220
2693
|
console.error(chalk.red('Error: JSON argument required.'));
|
|
1221
|
-
console.error(chalk.dim(
|
|
2694
|
+
console.error(chalk.dim(` Usage: codeyam editor register '{"name":"DrinkCard - Default","componentName":"DrinkCard","url":"/isolated-components/DrinkCard?s=Default","dimensions":["${dim}"]}'`));
|
|
2695
|
+
console.error(chalk.dim(' For large payloads: codeyam editor register @/tmp/scenario.json'));
|
|
1222
2696
|
process.exit(1);
|
|
1223
2697
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
2698
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
2699
|
+
if (parsed.error) {
|
|
2700
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
2701
|
+
if (parsed.error === 'Invalid JSON') {
|
|
2702
|
+
console.error(chalk.dim(` Received: ${jsonArg.slice(0, 200)}`));
|
|
2703
|
+
}
|
|
1231
2704
|
process.exit(1);
|
|
1232
2705
|
}
|
|
2706
|
+
// Normalize to array for uniform handling
|
|
2707
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
1233
2708
|
const port = getServerPort();
|
|
1234
2709
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
2710
|
+
const isBatch = items.length > 1;
|
|
2711
|
+
let succeeded = 0;
|
|
2712
|
+
let failed = 0;
|
|
2713
|
+
for (let i = 0; i < items.length; i++) {
|
|
2714
|
+
const body = items[i];
|
|
2715
|
+
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
2716
|
+
try {
|
|
2717
|
+
const res = await fetch(url, {
|
|
2718
|
+
method: 'POST',
|
|
2719
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2720
|
+
body: JSON.stringify(body),
|
|
2721
|
+
});
|
|
2722
|
+
const data = await res.json();
|
|
2723
|
+
// Print concise summary instead of raw JSON so Claude doesn't need python3
|
|
2724
|
+
const parts = [];
|
|
2725
|
+
parts.push(`success=${data.success}`);
|
|
2726
|
+
if (data.scenario?.name)
|
|
2727
|
+
parts.push(`name="${data.scenario.name}"`);
|
|
2728
|
+
parts.push(`screenshot=${!!data.screenshotCaptured}`);
|
|
2729
|
+
if (data.capturedViewport) {
|
|
2730
|
+
parts.push(`viewport=${data.capturedViewport.width}×${data.capturedViewport.height}`);
|
|
2731
|
+
}
|
|
2732
|
+
if (data.scenario?.dimension)
|
|
2733
|
+
parts.push(`dimension="${data.scenario.dimension}"`);
|
|
2734
|
+
if (data.clientErrors?.length > 0) {
|
|
2735
|
+
parts.push(chalk.red(`errors=${data.clientErrors.length}`));
|
|
2736
|
+
}
|
|
2737
|
+
else {
|
|
2738
|
+
parts.push(`errors=0`);
|
|
2739
|
+
}
|
|
2740
|
+
if (data.seedResult) {
|
|
2741
|
+
if (data.seedResult.success) {
|
|
2742
|
+
parts.push(`seed=ok`);
|
|
2743
|
+
}
|
|
2744
|
+
else {
|
|
2745
|
+
parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (data.captureError)
|
|
2749
|
+
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
2750
|
+
console.log(prefix + parts.join(' '));
|
|
2751
|
+
// Warn when application scenario is missing seed data
|
|
2752
|
+
if (data.missingSeedWarning) {
|
|
2753
|
+
console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
|
|
2754
|
+
console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
|
|
2755
|
+
console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
|
|
2756
|
+
console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
|
|
2757
|
+
}
|
|
2758
|
+
// Surface client errors prominently so they can't be missed
|
|
2759
|
+
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
2760
|
+
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
2761
|
+
for (const err of data.clientErrors) {
|
|
2762
|
+
console.log(chalk.red(` → ${err}`));
|
|
2763
|
+
}
|
|
2764
|
+
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
2765
|
+
// Provide actionable debugging hints for HTTP/API errors
|
|
2766
|
+
const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
|
|
2767
|
+
const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
|
|
2768
|
+
if (hasHttpError || hasApiResponseError) {
|
|
2769
|
+
console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
|
|
2770
|
+
console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
|
|
2771
|
+
console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
|
|
2772
|
+
}
|
|
2773
|
+
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2774
|
+
}
|
|
2775
|
+
if (!res.ok) {
|
|
2776
|
+
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2777
|
+
failed++;
|
|
2778
|
+
}
|
|
2779
|
+
else {
|
|
2780
|
+
succeeded++;
|
|
2781
|
+
}
|
|
1245
2782
|
}
|
|
2783
|
+
catch (error) {
|
|
2784
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2785
|
+
console.error(prefix + chalk.red(`Error: Could not reach editor server at ${url}`));
|
|
2786
|
+
console.error(chalk.dim(` ${msg}`));
|
|
2787
|
+
console.error(chalk.dim(' Is the editor running? Start it with: codeyam editor'));
|
|
2788
|
+
failed++;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
if (isBatch) {
|
|
2792
|
+
console.log(chalk.dim(`\nBatch complete: ${succeeded}/${items.length} succeeded`));
|
|
2793
|
+
}
|
|
2794
|
+
if (failed > 0) {
|
|
2795
|
+
process.exit(1);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
// ─── Glossary-add subcommand ──────────────────────────────────────────
|
|
2799
|
+
/**
|
|
2800
|
+
* `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
|
|
2801
|
+
*
|
|
2802
|
+
* Safely adds/updates entries in .codeyam/glossary.json via the CLI,
|
|
2803
|
+
* avoiding hand-editing that breaks on unicode characters.
|
|
2804
|
+
*/
|
|
2805
|
+
async function handleGlossaryAdd(jsonArg) {
|
|
2806
|
+
if (!jsonArg) {
|
|
2807
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
2808
|
+
console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
|
|
2809
|
+
console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
|
|
2810
|
+
process.exit(1);
|
|
2811
|
+
}
|
|
2812
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
2813
|
+
if (parsed.error) {
|
|
2814
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
2815
|
+
process.exit(1);
|
|
1246
2816
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
2817
|
+
// Normalize to array
|
|
2818
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
2819
|
+
// Read existing glossary
|
|
2820
|
+
const root = getProjectRoot();
|
|
2821
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2822
|
+
let existing = [];
|
|
2823
|
+
try {
|
|
2824
|
+
const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2825
|
+
existing = sanitizeGlossaryEntries(raw);
|
|
2826
|
+
}
|
|
2827
|
+
catch {
|
|
2828
|
+
// Glossary doesn't exist yet or can't be parsed — start fresh
|
|
2829
|
+
}
|
|
2830
|
+
// Merge
|
|
2831
|
+
const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
|
|
2832
|
+
const result = mergeGlossaryEntries(existing, items);
|
|
2833
|
+
// Report validation errors
|
|
2834
|
+
for (const err of result.errors) {
|
|
2835
|
+
console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
|
|
2836
|
+
}
|
|
2837
|
+
if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
|
|
1252
2838
|
process.exit(1);
|
|
1253
2839
|
}
|
|
2840
|
+
// Write back with utf8 encoding to safely handle unicode
|
|
2841
|
+
fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
|
|
2842
|
+
fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
|
|
2843
|
+
console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
|
|
1254
2844
|
}
|
|
1255
2845
|
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
1256
2846
|
/**
|
|
@@ -1269,11 +2859,23 @@ async function handleDependents(entityName) {
|
|
|
1269
2859
|
const progress = new ProgressReporter();
|
|
1270
2860
|
progress.start('Loading entities from database...');
|
|
1271
2861
|
await initializeEnvironment();
|
|
1272
|
-
|
|
2862
|
+
let allEntities = await loadEntities({});
|
|
1273
2863
|
if (!allEntities || allEntities.length === 0) {
|
|
1274
|
-
progress.
|
|
1275
|
-
|
|
1276
|
-
|
|
2864
|
+
progress.succeed('No entities found — running analyze-imports first...');
|
|
2865
|
+
try {
|
|
2866
|
+
await handleAnalyzeImports({ silent: true });
|
|
2867
|
+
}
|
|
2868
|
+
catch {
|
|
2869
|
+
console.error(chalk.red('Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
2870
|
+
process.exit(1);
|
|
2871
|
+
}
|
|
2872
|
+
// Reload entities after analysis
|
|
2873
|
+
const reloaded = await loadEntities({});
|
|
2874
|
+
if (!reloaded || reloaded.length === 0) {
|
|
2875
|
+
progress.fail('No entities found even after analyze-imports');
|
|
2876
|
+
process.exit(1);
|
|
2877
|
+
}
|
|
2878
|
+
allEntities = reloaded;
|
|
1277
2879
|
}
|
|
1278
2880
|
// Find the target entity by name (case-insensitive match)
|
|
1279
2881
|
const targetEntities = allEntities.filter((e) => e.name.toLowerCase() === entityName.toLowerCase());
|
|
@@ -1360,15 +2962,16 @@ async function handleDependents(entityName) {
|
|
|
1360
2962
|
* `codeyam editor change <feature>`
|
|
1361
2963
|
*
|
|
1362
2964
|
* Prints a condensed post-change checklist that guides Claude through
|
|
1363
|
-
* re-verifying after user-requested modifications.
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
2965
|
+
* re-verifying after user-requested modifications. When called from
|
|
2966
|
+
* step 15+, this loops back to step 15 (present). When called from an
|
|
2967
|
+
* earlier step, it returns to that step so the normal flow continues.
|
|
1366
2968
|
*/
|
|
1367
2969
|
function handleChange(feature) {
|
|
1368
2970
|
const root = getProjectRoot();
|
|
2971
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
2972
|
+
const state = readState(root);
|
|
1369
2973
|
if (!feature) {
|
|
1370
2974
|
// Try to read feature from state
|
|
1371
|
-
const state = readState(root);
|
|
1372
2975
|
if (state?.feature) {
|
|
1373
2976
|
feature = state.feature;
|
|
1374
2977
|
}
|
|
@@ -1378,6 +2981,7 @@ function handleChange(feature) {
|
|
|
1378
2981
|
process.exit(1);
|
|
1379
2982
|
}
|
|
1380
2983
|
}
|
|
2984
|
+
const currentStep = state?.step ?? 15;
|
|
1381
2985
|
const port = getServerPort();
|
|
1382
2986
|
console.log();
|
|
1383
2987
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -1385,9 +2989,19 @@ function handleChange(feature) {
|
|
|
1385
2989
|
console.log();
|
|
1386
2990
|
console.log('The user has requested changes. Follow this checklist after making them.');
|
|
1387
2991
|
console.log();
|
|
2992
|
+
const designSystem = readDesignSystem(root);
|
|
2993
|
+
if (designSystem) {
|
|
2994
|
+
console.log(chalk.bold.magenta('Design System (active):'));
|
|
2995
|
+
console.log(chalk.magenta(' Use existing CSS custom property tokens when making visual changes — no hardcoded px or hex values.'));
|
|
2996
|
+
console.log(chalk.magenta(' Use var(--text-sm) not 14, var(--spacing-lg) not 16px, var(--text-primary) not #1A1B25.'));
|
|
2997
|
+
console.log();
|
|
2998
|
+
console.log(designSystem);
|
|
2999
|
+
console.log();
|
|
3000
|
+
}
|
|
1388
3001
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
1389
3002
|
console.log(chalk.cyan(` Refresh after EACH individual change — not after all changes are done:`));
|
|
1390
|
-
console.log(chalk.cyan(` codeyam editor preview`));
|
|
3003
|
+
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
3004
|
+
printDimensionGuidance(dim, dimNames);
|
|
1391
3005
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
1392
3006
|
console.log();
|
|
1393
3007
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
@@ -1395,8 +3009,8 @@ function handleChange(feature) {
|
|
|
1395
3009
|
console.log();
|
|
1396
3010
|
console.log(chalk.bold('1. Re-register affected component scenarios:'));
|
|
1397
3011
|
checkbox('For each component you modified, re-register ALL its scenarios');
|
|
1398
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - ScenarioName","
|
|
1399
|
-
checkbox('For any NEW components
|
|
3012
|
+
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}"]}'`));
|
|
3013
|
+
checkbox('For any NEW components: `codeyam editor isolate NewComponent` then create isolation routes and register scenarios');
|
|
1400
3014
|
console.log();
|
|
1401
3015
|
console.log(chalk.bold('2. Re-run affected tests:'));
|
|
1402
3016
|
checkbox('Re-run test files for any functions you modified');
|
|
@@ -1406,15 +3020,31 @@ function handleChange(feature) {
|
|
|
1406
3020
|
console.log(chalk.dim(` codeyam editor dev-server '{"action":"restart"}'`));
|
|
1407
3021
|
console.log();
|
|
1408
3022
|
console.log(chalk.bold('3. Re-capture app-level scenarios:'));
|
|
1409
|
-
checkbox('
|
|
1410
|
-
checkbox(`
|
|
3023
|
+
checkbox('For each changed component, run `codeyam editor dependents ComponentName` to find affected pages');
|
|
3024
|
+
checkbox('Run `codeyam editor scenarios` to list all existing scenarios');
|
|
3025
|
+
checkbox('For EACH page listed by dependents: find its existing scenarios in the list and re-register every one');
|
|
3026
|
+
console.log(chalk.dim(' Shared components (headers, navbars, layouts) affect MANY pages — re-register all of them.'));
|
|
3027
|
+
console.log(chalk.dim(' Re-register with the SAME name to update — do NOT create new/duplicate scenarios.'));
|
|
3028
|
+
console.log(chalk.dim(' Example: if Header changed and Home, Catalog, Detail pages use it,'));
|
|
3029
|
+
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
3030
|
+
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
3031
|
+
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
3032
|
+
console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
|
|
3033
|
+
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
3034
|
+
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
1411
3035
|
console.log();
|
|
1412
3036
|
console.log(chalk.bold('4. Verify completeness:'));
|
|
1413
|
-
checkbox(`Refresh the preview: \`codeyam editor preview\``);
|
|
1414
|
-
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route"}'\``);
|
|
3037
|
+
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
3038
|
+
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
3039
|
+
printDimensionGuidance(dim, dimNames);
|
|
3040
|
+
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
3041
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1415
3042
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
1416
3043
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
3044
|
+
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
3045
|
+
console.log(chalk.dim(' If `liveErrors>0`, there are JS errors in the preview — fix them.'));
|
|
1417
3046
|
checkbox('Fix any errors, then re-register affected scenarios');
|
|
3047
|
+
checkbox('If changes affect setup, new dependencies, or new scripts: update `README.md` and `npm run setup`');
|
|
1418
3048
|
console.log();
|
|
1419
3049
|
console.log(chalk.bold('5. Update the existing journal entry:'));
|
|
1420
3050
|
checkbox('Get existing entry: `codeyam editor journal-list`');
|
|
@@ -1423,11 +3053,171 @@ function handleChange(feature) {
|
|
|
1423
3053
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
1424
3054
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
1425
3055
|
console.log();
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
3056
|
+
// If the change was initiated from a step before 15, return to that step
|
|
3057
|
+
// instead of jumping to step 15. The change workflow should only loop to
|
|
3058
|
+
// step 15 when changes are requested FROM step 15.
|
|
3059
|
+
if (currentStep < 15) {
|
|
3060
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
3061
|
+
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
3062
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
3063
|
+
chalk.bold(`codeyam editor ${currentStep}`));
|
|
3064
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
3065
|
+
}
|
|
3066
|
+
else {
|
|
3067
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
3068
|
+
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
3069
|
+
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
3070
|
+
chalk.bold(`codeyam editor 15`));
|
|
3071
|
+
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
3072
|
+
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
3073
|
+
}
|
|
1429
3074
|
console.log();
|
|
1430
3075
|
}
|
|
3076
|
+
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
3077
|
+
/**
|
|
3078
|
+
* Fetch the audit result from the server.
|
|
3079
|
+
* Returns null if the server is unreachable.
|
|
3080
|
+
*/
|
|
3081
|
+
async function fetchAuditResult() {
|
|
3082
|
+
const port = getServerPort();
|
|
3083
|
+
try {
|
|
3084
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3085
|
+
if (!res.ok)
|
|
3086
|
+
return null;
|
|
3087
|
+
return await res.json();
|
|
3088
|
+
}
|
|
3089
|
+
catch {
|
|
3090
|
+
return null;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
3095
|
+
* false if any issues remain or the server is unreachable.
|
|
3096
|
+
* Used as a hard gate before steps 8+.
|
|
3097
|
+
*
|
|
3098
|
+
* Auto-runs analyze-imports if the only failure is incomplete entities,
|
|
3099
|
+
* then re-checks once.
|
|
3100
|
+
*/
|
|
3101
|
+
async function checkAuditGate() {
|
|
3102
|
+
const data = await fetchAuditResult();
|
|
3103
|
+
if (!data)
|
|
3104
|
+
return true; // Server unreachable — don't block
|
|
3105
|
+
if (data.summary?.allPassing === true)
|
|
3106
|
+
return true;
|
|
3107
|
+
// If incomplete entities are the only issue, auto-fix with analyze-imports (once)
|
|
3108
|
+
const { isAutoRemediable } = await import('../utils/editorAudit.js');
|
|
3109
|
+
if (isAutoRemediable(data.summary, false)) {
|
|
3110
|
+
try {
|
|
3111
|
+
await handleAnalyzeImports({ silent: true });
|
|
3112
|
+
}
|
|
3113
|
+
catch {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
// Re-check after fix
|
|
3117
|
+
const retry = await fetchAuditResult();
|
|
3118
|
+
if (retry?.summary?.allPassing === true)
|
|
3119
|
+
return true;
|
|
3120
|
+
}
|
|
3121
|
+
// Print specific failures so Claude knows what to fix without running audit separately
|
|
3122
|
+
printAuditGateFailures(data);
|
|
3123
|
+
return false;
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Print a concise summary of audit failures for the gate block message.
|
|
3127
|
+
* This gives Claude immediate context about what to fix without needing
|
|
3128
|
+
* to run `codeyam editor audit` as a separate step.
|
|
3129
|
+
*/
|
|
3130
|
+
function printAuditGateFailures(data) {
|
|
3131
|
+
const s = data.summary;
|
|
3132
|
+
if (!s)
|
|
3133
|
+
return;
|
|
3134
|
+
const issues = [];
|
|
3135
|
+
if (s.componentsMissing > 0) {
|
|
3136
|
+
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
3137
|
+
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3138
|
+
for (const c of missing) {
|
|
3139
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
if (s.componentsWithErrors > 0) {
|
|
3143
|
+
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
3144
|
+
const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
|
|
3145
|
+
for (const c of withErrors) {
|
|
3146
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
if (s.functionsMissing > 0) {
|
|
3150
|
+
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3151
|
+
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3152
|
+
for (const f of missing) {
|
|
3153
|
+
issues.push(` → ${f.name}${f.filePath ? ` (${f.filePath})` : ''}`);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
if (s.functionsFailing > 0) {
|
|
3157
|
+
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
3158
|
+
const failing = (data.functions || []).filter((f) => f.status === 'failing');
|
|
3159
|
+
for (const f of failing) {
|
|
3160
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (s.functionsRunnerError > 0) {
|
|
3164
|
+
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
3165
|
+
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3166
|
+
for (const f of runnerErrors) {
|
|
3167
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
if (s.functionsNameMismatch > 0) {
|
|
3171
|
+
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
3172
|
+
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3173
|
+
for (const f of mismatch) {
|
|
3174
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
if (s.missingFromGlossary > 0)
|
|
3178
|
+
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3179
|
+
if (s.incompleteEntities > 0) {
|
|
3180
|
+
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3181
|
+
if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3182
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3183
|
+
}
|
|
3184
|
+
else if (preCount > 0) {
|
|
3185
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
|
|
3186
|
+
}
|
|
3187
|
+
else {
|
|
3188
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
if (s.miscategorizedScenarios > 0)
|
|
3192
|
+
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3193
|
+
if (s.scenariosNeedingRecapture > 0)
|
|
3194
|
+
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
3195
|
+
if (issues.length > 0) {
|
|
3196
|
+
console.error(chalk.yellow('\nAudit failures:'));
|
|
3197
|
+
for (const issue of issues) {
|
|
3198
|
+
console.error(chalk.yellow(` • ${issue}`));
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
// Surface client error details inline — these are the most common cause of
|
|
3202
|
+
// Claude looping because the errors require code fixes, not audit re-runs.
|
|
3203
|
+
if (data.components) {
|
|
3204
|
+
const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
|
|
3205
|
+
if (withErrors.length > 0) {
|
|
3206
|
+
console.error(chalk.yellow('\nClient errors found:'));
|
|
3207
|
+
for (const c of withErrors) {
|
|
3208
|
+
console.error(chalk.red(` ${c.name}:`));
|
|
3209
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3210
|
+
console.error(chalk.red(` → ${err}`));
|
|
3211
|
+
}
|
|
3212
|
+
if (c.clientErrors.length > 3) {
|
|
3213
|
+
console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
3220
|
+
}
|
|
1431
3221
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
1432
3222
|
/**
|
|
1433
3223
|
* `codeyam editor audit`
|
|
@@ -1437,35 +3227,82 @@ function handleChange(feature) {
|
|
|
1437
3227
|
* have test files. Exits with code 1 if anything is missing.
|
|
1438
3228
|
*/
|
|
1439
3229
|
async function handleAudit() {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
let data;
|
|
1443
|
-
try {
|
|
1444
|
-
const res = await fetch(url);
|
|
1445
|
-
if (!res.ok) {
|
|
1446
|
-
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
1447
|
-
process.exit(1);
|
|
1448
|
-
}
|
|
1449
|
-
data = await res.json();
|
|
1450
|
-
}
|
|
1451
|
-
catch (err) {
|
|
3230
|
+
let data = await fetchAuditResult();
|
|
3231
|
+
if (!data) {
|
|
1452
3232
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
1453
|
-
console.error(chalk.dim(` ${err.message}`));
|
|
1454
3233
|
process.exit(1);
|
|
1455
3234
|
}
|
|
3235
|
+
// Auto-fix incomplete entities — but only once.
|
|
3236
|
+
// If analyze-imports runs and entities are STILL incomplete, report clearly
|
|
3237
|
+
// instead of retrying. The analysis may have errors (e.g., stale entity
|
|
3238
|
+
// references from refactoring) that can't be fixed by re-running.
|
|
3239
|
+
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3240
|
+
let autoRemediationFailed = false;
|
|
3241
|
+
if (incompleteBeforeFix.length > 0) {
|
|
3242
|
+
console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
|
|
3243
|
+
try {
|
|
3244
|
+
await handleAnalyzeImports({ silent: true });
|
|
3245
|
+
}
|
|
3246
|
+
catch {
|
|
3247
|
+
// Fall through — the audit will still report them as incomplete
|
|
3248
|
+
}
|
|
3249
|
+
// Re-fetch audit results after the fix
|
|
3250
|
+
data = await fetchAuditResult();
|
|
3251
|
+
if (!data) {
|
|
3252
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
|
|
3253
|
+
process.exit(1);
|
|
3254
|
+
}
|
|
3255
|
+
// If entities are still incomplete after running analyze-imports,
|
|
3256
|
+
// flag it so we can show a clear message instead of looping
|
|
3257
|
+
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3258
|
+
if (incompleteAfterFix.length > 0) {
|
|
3259
|
+
autoRemediationFailed = true;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
1456
3262
|
const { components, functions, summary } = data;
|
|
1457
3263
|
console.log();
|
|
1458
3264
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
1459
3265
|
console.log();
|
|
1460
3266
|
// Components
|
|
1461
3267
|
if (components.length > 0) {
|
|
3268
|
+
// Build name frequency map for disambiguation
|
|
3269
|
+
const componentNameCounts = new Map();
|
|
3270
|
+
for (const c of components) {
|
|
3271
|
+
componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
|
|
3272
|
+
}
|
|
1462
3273
|
console.log(chalk.bold('Components (scenarios):'));
|
|
1463
3274
|
for (const c of components) {
|
|
1464
|
-
const icon = c.status === 'ok'
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
3275
|
+
const icon = c.status === 'ok'
|
|
3276
|
+
? chalk.green('✓')
|
|
3277
|
+
: c.status === 'needs_recapture'
|
|
3278
|
+
? chalk.yellow('↻')
|
|
3279
|
+
: chalk.red('✗');
|
|
3280
|
+
let detail;
|
|
3281
|
+
if (c.status === 'has_errors') {
|
|
3282
|
+
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
3283
|
+
}
|
|
3284
|
+
else if (c.status === 'needs_recapture') {
|
|
3285
|
+
detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
|
|
3286
|
+
}
|
|
3287
|
+
else if (c.status === 'ok') {
|
|
3288
|
+
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
3289
|
+
}
|
|
3290
|
+
else {
|
|
3291
|
+
detail = chalk.red(' — no scenarios registered');
|
|
3292
|
+
}
|
|
3293
|
+
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3294
|
+
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
3295
|
+
const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
|
|
3296
|
+
const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
|
|
3297
|
+
console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
|
|
3298
|
+
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
3299
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3300
|
+
console.log(chalk.red(` → ${err}`));
|
|
3301
|
+
}
|
|
3302
|
+
if (c.clientErrors.length > 3) {
|
|
3303
|
+
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
1469
3306
|
}
|
|
1470
3307
|
console.log();
|
|
1471
3308
|
}
|
|
@@ -1479,6 +3316,14 @@ async function handleAudit() {
|
|
|
1479
3316
|
case 'ok':
|
|
1480
3317
|
detail = chalk.dim(` (${f.testFile})`);
|
|
1481
3318
|
break;
|
|
3319
|
+
case 'runner_error':
|
|
3320
|
+
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3321
|
+
if (f.errorMessage) {
|
|
3322
|
+
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3323
|
+
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3324
|
+
detail += `\n ${chalk.yellow('Try running the test manually: npx vitest run ' + f.testFile)}`;
|
|
3325
|
+
}
|
|
3326
|
+
break;
|
|
1482
3327
|
case 'failing':
|
|
1483
3328
|
detail = chalk.red(` — tests failing: ${f.testFile}`);
|
|
1484
3329
|
break;
|
|
@@ -1496,6 +3341,77 @@ async function handleAudit() {
|
|
|
1496
3341
|
}
|
|
1497
3342
|
console.log();
|
|
1498
3343
|
}
|
|
3344
|
+
// Missing from glossary
|
|
3345
|
+
const missingFromGlossary = data.missingFromGlossary || [];
|
|
3346
|
+
if (missingFromGlossary.length > 0) {
|
|
3347
|
+
console.log(chalk.bold('Missing from glossary:'));
|
|
3348
|
+
for (const m of missingFromGlossary) {
|
|
3349
|
+
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3350
|
+
}
|
|
3351
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3352
|
+
console.log();
|
|
3353
|
+
}
|
|
3354
|
+
// Incomplete entities (scenarios without analyses)
|
|
3355
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3356
|
+
if (incompleteEntities.length > 0) {
|
|
3357
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3358
|
+
for (const e of incompleteEntities) {
|
|
3359
|
+
console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
|
|
3360
|
+
}
|
|
3361
|
+
if (autoRemediationFailed) {
|
|
3362
|
+
console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
|
|
3363
|
+
console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
|
|
3364
|
+
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3365
|
+
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3366
|
+
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3367
|
+
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
|
|
3371
|
+
}
|
|
3372
|
+
console.log();
|
|
3373
|
+
}
|
|
3374
|
+
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
3375
|
+
const miscategorizedScenarios = data.miscategorizedScenarios || [];
|
|
3376
|
+
if (miscategorizedScenarios.length > 0) {
|
|
3377
|
+
console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
|
|
3378
|
+
for (const m of miscategorizedScenarios) {
|
|
3379
|
+
console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
|
|
3380
|
+
for (const name of m.scenarioNames) {
|
|
3381
|
+
console.log(chalk.dim(` "${name}"`));
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
|
|
3385
|
+
console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
|
|
3386
|
+
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
3387
|
+
console.log();
|
|
3388
|
+
}
|
|
3389
|
+
// Scenarios needing recapture (entity or dependency tree changed)
|
|
3390
|
+
const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
|
|
3391
|
+
if (scenariosNeedingRecapture.length > 0) {
|
|
3392
|
+
console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
|
|
3393
|
+
for (const s of scenariosNeedingRecapture) {
|
|
3394
|
+
const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
|
|
3395
|
+
? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
|
|
3396
|
+
: `${s.status.status}`;
|
|
3397
|
+
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3398
|
+
}
|
|
3399
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
3400
|
+
console.log();
|
|
3401
|
+
}
|
|
3402
|
+
// Duplicate glossary names (warning, not a failure)
|
|
3403
|
+
const duplicateNames = data.duplicateNames || [];
|
|
3404
|
+
if (duplicateNames.length > 0) {
|
|
3405
|
+
console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
|
|
3406
|
+
for (const dn of duplicateNames) {
|
|
3407
|
+
console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
|
|
3408
|
+
for (const fp of dn.filePaths) {
|
|
3409
|
+
console.log(` ${fp}`);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
|
|
3413
|
+
console.log();
|
|
3414
|
+
}
|
|
1499
3415
|
// Summary
|
|
1500
3416
|
const allOk = summary.allPassing;
|
|
1501
3417
|
if (allOk) {
|
|
@@ -1507,21 +3423,153 @@ async function handleAudit() {
|
|
|
1507
3423
|
if (summary.componentsMissing > 0) {
|
|
1508
3424
|
parts.push(`${summary.componentsMissing} component${summary.componentsMissing !== 1 ? 's' : ''} missing scenarios`);
|
|
1509
3425
|
}
|
|
3426
|
+
if (summary.componentsWithErrors > 0) {
|
|
3427
|
+
parts.push(`${summary.componentsWithErrors} component${summary.componentsWithErrors !== 1 ? 's' : ''} with client errors`);
|
|
3428
|
+
}
|
|
1510
3429
|
if (summary.functionsMissing > 0) {
|
|
1511
3430
|
parts.push(`${summary.functionsMissing} function${summary.functionsMissing !== 1 ? 's' : ''} missing tests`);
|
|
1512
3431
|
}
|
|
1513
3432
|
if (summary.functionsFailing > 0) {
|
|
1514
3433
|
parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
|
|
1515
3434
|
}
|
|
3435
|
+
if (summary.functionsRunnerError > 0) {
|
|
3436
|
+
parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
|
|
3437
|
+
}
|
|
1516
3438
|
if (summary.functionsNameMismatch > 0) {
|
|
1517
3439
|
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
1518
3440
|
}
|
|
3441
|
+
if (summary.missingFromGlossary > 0) {
|
|
3442
|
+
parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
|
|
3443
|
+
}
|
|
3444
|
+
if (summary.incompleteEntities > 0) {
|
|
3445
|
+
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3446
|
+
}
|
|
3447
|
+
if (summary.miscategorizedScenarios > 0) {
|
|
3448
|
+
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3449
|
+
}
|
|
3450
|
+
if (summary.scenariosNeedingRecapture > 0) {
|
|
3451
|
+
parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
|
|
3452
|
+
}
|
|
1519
3453
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
1520
3454
|
}
|
|
1521
3455
|
console.log();
|
|
1522
3456
|
if (!allOk) {
|
|
1523
3457
|
process.exit(1);
|
|
1524
3458
|
}
|
|
3459
|
+
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3460
|
+
// so the App tab can show component/function dependencies for each page.
|
|
3461
|
+
// Previously this was a manual step that Claude had to remember to run.
|
|
3462
|
+
console.log(chalk.dim('Building import graph...'));
|
|
3463
|
+
try {
|
|
3464
|
+
await handleAnalyzeImports({ silent: true });
|
|
3465
|
+
}
|
|
3466
|
+
catch {
|
|
3467
|
+
// Non-fatal — audit passed, import graph is a bonus
|
|
3468
|
+
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3472
|
+
/**
|
|
3473
|
+
* `codeyam editor recapture-stale`
|
|
3474
|
+
*
|
|
3475
|
+
* Identifies all scenarios whose entity (or dependency) has changed but
|
|
3476
|
+
* whose screenshot hasn't been recaptured this session, then recaptures
|
|
3477
|
+
* them in batch.
|
|
3478
|
+
*/
|
|
3479
|
+
async function handleRecaptureStale() {
|
|
3480
|
+
const port = getServerPort();
|
|
3481
|
+
const url = `http://localhost:${port}/api/editor-recapture-stale`;
|
|
3482
|
+
console.log();
|
|
3483
|
+
console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
|
|
3484
|
+
console.log();
|
|
3485
|
+
let res;
|
|
3486
|
+
try {
|
|
3487
|
+
res = await fetch(url, { method: 'POST' });
|
|
3488
|
+
}
|
|
3489
|
+
catch (err) {
|
|
3490
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3491
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
3492
|
+
process.exit(1);
|
|
3493
|
+
}
|
|
3494
|
+
if (!res.ok) {
|
|
3495
|
+
const body = await res.json().catch(() => null);
|
|
3496
|
+
console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
|
|
3497
|
+
if (body?.error)
|
|
3498
|
+
console.error(chalk.red(` ${body.error}`));
|
|
3499
|
+
process.exit(1);
|
|
3500
|
+
}
|
|
3501
|
+
// Handle JSON response (early returns like "no changes" / "all up to date")
|
|
3502
|
+
const contentType = res.headers.get('content-type') || '';
|
|
3503
|
+
if (contentType.includes('application/json')) {
|
|
3504
|
+
const data = await res.json();
|
|
3505
|
+
if (data.note) {
|
|
3506
|
+
console.log(chalk.dim(data.note));
|
|
3507
|
+
}
|
|
3508
|
+
else if (data.total === 0) {
|
|
3509
|
+
console.log(chalk.green('All scenarios are up to date.'));
|
|
3510
|
+
}
|
|
3511
|
+
console.log();
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
// Stream NDJSON progress events
|
|
3515
|
+
let total = 0;
|
|
3516
|
+
let recapturedCount = 0;
|
|
3517
|
+
let failedCount = 0;
|
|
3518
|
+
const reader = res.body?.getReader();
|
|
3519
|
+
if (!reader) {
|
|
3520
|
+
console.error(chalk.red('Error: No response body'));
|
|
3521
|
+
process.exit(1);
|
|
3522
|
+
}
|
|
3523
|
+
const decoder = new TextDecoder();
|
|
3524
|
+
let buffer = '';
|
|
3525
|
+
while (true) {
|
|
3526
|
+
const { done, value } = await reader.read();
|
|
3527
|
+
if (done)
|
|
3528
|
+
break;
|
|
3529
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3530
|
+
const lines = buffer.split('\n');
|
|
3531
|
+
buffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
3532
|
+
for (const line of lines) {
|
|
3533
|
+
if (!line.trim())
|
|
3534
|
+
continue;
|
|
3535
|
+
try {
|
|
3536
|
+
const event = JSON.parse(line);
|
|
3537
|
+
switch (event.type) {
|
|
3538
|
+
case 'start':
|
|
3539
|
+
total = event.total;
|
|
3540
|
+
console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
|
|
3541
|
+
break;
|
|
3542
|
+
case 'capturing':
|
|
3543
|
+
process.stdout.write(chalk.dim(` … ${event.name}`));
|
|
3544
|
+
break;
|
|
3545
|
+
case 'success':
|
|
3546
|
+
// Clear the "capturing" line and print success
|
|
3547
|
+
process.stdout.write('\r\x1b[K');
|
|
3548
|
+
console.log(` ${chalk.green('✓')} ${event.name}`);
|
|
3549
|
+
recapturedCount++;
|
|
3550
|
+
break;
|
|
3551
|
+
case 'failure':
|
|
3552
|
+
process.stdout.write('\r\x1b[K');
|
|
3553
|
+
console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
|
|
3554
|
+
failedCount++;
|
|
3555
|
+
break;
|
|
3556
|
+
case 'done':
|
|
3557
|
+
// Final summary
|
|
3558
|
+
console.log();
|
|
3559
|
+
const color = failedCount > 0 ? chalk.yellow : chalk.green;
|
|
3560
|
+
console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
|
|
3561
|
+
console.log();
|
|
3562
|
+
break;
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
catch {
|
|
3566
|
+
// Skip unparseable lines
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
if (failedCount > 0) {
|
|
3571
|
+
process.exit(1);
|
|
3572
|
+
}
|
|
1525
3573
|
}
|
|
1526
3574
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
1527
3575
|
async function handleScenarios() {
|
|
@@ -1609,6 +3657,103 @@ async function handleScenarios() {
|
|
|
1609
3657
|
const total = scenarios.length;
|
|
1610
3658
|
const groupCount = groups.size;
|
|
1611
3659
|
console.log(chalk.dim(`${total} scenario${total !== 1 ? 's' : ''} across ${groupCount} component${groupCount !== 1 ? 's' : ''}`));
|
|
3660
|
+
// Show screenshot paths so Claude can verify images with the Read tool
|
|
3661
|
+
const withScreenshots = scenarios.filter((s) => s.screenshotPath);
|
|
3662
|
+
if (withScreenshots.length > 0) {
|
|
3663
|
+
console.log();
|
|
3664
|
+
console.log(chalk.bold.cyan('Screenshots:'));
|
|
3665
|
+
for (const s of withScreenshots) {
|
|
3666
|
+
console.log(chalk.dim(` ${s.name}: ${s.screenshotPath}`));
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
console.log();
|
|
3670
|
+
}
|
|
3671
|
+
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
3672
|
+
async function handleScenarioCoverage() {
|
|
3673
|
+
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3674
|
+
try {
|
|
3675
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3676
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
3677
|
+
const db = getDatabase();
|
|
3678
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3679
|
+
if (backfillCount > 0) {
|
|
3680
|
+
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3681
|
+
await handleAnalyzeImports({ silent: true });
|
|
3682
|
+
// Run backfill after analysis
|
|
3683
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
3684
|
+
const entities = await loadEntities({});
|
|
3685
|
+
if (entities && entities.length > 0) {
|
|
3686
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3687
|
+
sha: e.sha,
|
|
3688
|
+
name: e.name,
|
|
3689
|
+
filePath: e.filePath || '',
|
|
3690
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3691
|
+
e.metadata?.namedExport === false,
|
|
3692
|
+
})));
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
catch {
|
|
3697
|
+
// Non-fatal — proceed with coverage check regardless
|
|
3698
|
+
}
|
|
3699
|
+
const port = getServerPort();
|
|
3700
|
+
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
3701
|
+
let data;
|
|
3702
|
+
try {
|
|
3703
|
+
const res = await fetch(url);
|
|
3704
|
+
if (!res.ok) {
|
|
3705
|
+
console.error(chalk.red(`Error: Scenario coverage endpoint returned ${res.status}`));
|
|
3706
|
+
process.exit(1);
|
|
3707
|
+
}
|
|
3708
|
+
data = await res.json();
|
|
3709
|
+
}
|
|
3710
|
+
catch (err) {
|
|
3711
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3712
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
3713
|
+
process.exit(1);
|
|
3714
|
+
}
|
|
3715
|
+
console.log();
|
|
3716
|
+
console.log(chalk.bold.cyan('━━━ Scenario Coverage ━━━'));
|
|
3717
|
+
console.log();
|
|
3718
|
+
if (data.note) {
|
|
3719
|
+
console.log(chalk.yellow(data.note));
|
|
3720
|
+
console.log();
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
// Fresh scenarios (already recaptured this feature)
|
|
3724
|
+
if (data.freshScenarios.length > 0) {
|
|
3725
|
+
console.log(chalk.green(`✓ ${data.freshScenarios.length} scenario(s) captured this feature:`));
|
|
3726
|
+
for (const s of data.freshScenarios) {
|
|
3727
|
+
console.log(chalk.dim(` ${s.name} (${s.url || '/'})`));
|
|
3728
|
+
}
|
|
3729
|
+
console.log();
|
|
3730
|
+
}
|
|
3731
|
+
// Stale scenarios (need recapturing)
|
|
3732
|
+
if (data.staleScenarios.length > 0) {
|
|
3733
|
+
console.log(chalk.red(`✗ ${data.staleScenarios.length} scenario(s) need re-registering (stale screenshots):`));
|
|
3734
|
+
for (const s of data.staleScenarios) {
|
|
3735
|
+
console.log(chalk.yellow(` ${s.name} (${s.url || '/'}) — ${s.changeStatus} but not recaptured`));
|
|
3736
|
+
}
|
|
3737
|
+
console.log();
|
|
3738
|
+
console.log(chalk.yellow('Re-register each stale scenario with the SAME name to update its screenshot.'));
|
|
3739
|
+
console.log(chalk.yellow("Enhance the seed data to reflect this feature's changes where relevant."));
|
|
3740
|
+
console.log();
|
|
3741
|
+
}
|
|
3742
|
+
// Uncovered pages (changed but no scenarios at all)
|
|
3743
|
+
if (data.uncoveredPages.length > 0) {
|
|
3744
|
+
console.log(chalk.red(`✗ ${data.uncoveredPages.length} page(s) affected by changes with no scenarios:`));
|
|
3745
|
+
for (const p of data.uncoveredPages) {
|
|
3746
|
+
console.log(chalk.yellow(` ${p.entityName} — ${p.changeStatus}`));
|
|
3747
|
+
}
|
|
3748
|
+
console.log();
|
|
3749
|
+
}
|
|
3750
|
+
// Summary
|
|
3751
|
+
if (data.pass) {
|
|
3752
|
+
console.log(chalk.green.bold('PASS — all affected scenarios are fresh'));
|
|
3753
|
+
}
|
|
3754
|
+
else {
|
|
3755
|
+
console.log(chalk.red.bold('FAIL — re-register stale scenarios before proceeding'));
|
|
3756
|
+
}
|
|
1612
3757
|
console.log();
|
|
1613
3758
|
}
|
|
1614
3759
|
// ─── Template subcommand ─────────────────────────────────────────────
|
|
@@ -1619,20 +3764,44 @@ async function handleTemplate() {
|
|
|
1619
3764
|
console.log(chalk.yellow('Project already exists (package.json found). Skipping.'));
|
|
1620
3765
|
return;
|
|
1621
3766
|
}
|
|
1622
|
-
|
|
3767
|
+
// Determine which template to use from editor state
|
|
3768
|
+
const state = readState(root);
|
|
3769
|
+
const techStackId = state?.techStackId;
|
|
3770
|
+
const stack = techStackId
|
|
3771
|
+
? TECH_STACKS.find((s) => s.id === techStackId)
|
|
3772
|
+
: undefined;
|
|
3773
|
+
const templateName = stack?.templateDir || 'nextjs-prisma-sqlite';
|
|
3774
|
+
const templateDir = path.join(__dirname, '..', '..', 'templates', templateName);
|
|
1623
3775
|
if (!fs.existsSync(templateDir)) {
|
|
1624
3776
|
console.error(chalk.red('Error: Template directory not found at ' + templateDir));
|
|
1625
3777
|
process.exit(1);
|
|
1626
3778
|
}
|
|
1627
3779
|
const { execSync } = await import('child_process');
|
|
1628
3780
|
// 1. Copy template files
|
|
1629
|
-
console.log(chalk.bold(
|
|
3781
|
+
console.log(chalk.bold(`Copying ${stack?.name || templateName} template files...`));
|
|
1630
3782
|
execSync(`cp -r ${templateDir}/* .`, { cwd: root, stdio: 'inherit' });
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
3783
|
+
// Copy dotfiles that are stored without the dot prefix in templates
|
|
3784
|
+
const gitignorePath = path.join(templateDir, 'gitignore');
|
|
3785
|
+
if (fs.existsSync(gitignorePath)) {
|
|
3786
|
+
execSync(`cp ${templateDir}/gitignore .gitignore`, {
|
|
3787
|
+
cwd: root,
|
|
3788
|
+
stdio: 'inherit',
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
const envPath = path.join(templateDir, 'env');
|
|
3792
|
+
if (fs.existsSync(envPath)) {
|
|
3793
|
+
execSync(`cp ${templateDir}/env .env`, { cwd: root, stdio: 'inherit' });
|
|
3794
|
+
}
|
|
3795
|
+
// Copy seed adapter into .codeyam/ (stored outside .codeyam/ in the template
|
|
3796
|
+
// to avoid gitignore issues — .codeyam/ is selectively ignored in user projects)
|
|
3797
|
+
const seedAdapterSrc = path.join(templateDir, 'seed-adapter.ts');
|
|
3798
|
+
if (fs.existsSync(seedAdapterSrc)) {
|
|
3799
|
+
const codeyamDir = path.join(root, '.codeyam');
|
|
3800
|
+
if (!fs.existsSync(codeyamDir)) {
|
|
3801
|
+
fs.mkdirSync(codeyamDir, { recursive: true });
|
|
3802
|
+
}
|
|
3803
|
+
fs.copyFileSync(seedAdapterSrc, path.join(codeyamDir, 'seed-adapter.ts'));
|
|
3804
|
+
}
|
|
1636
3805
|
console.log(chalk.green(' Template files copied.'));
|
|
1637
3806
|
// 2. Install dependencies
|
|
1638
3807
|
console.log(chalk.bold('Installing dependencies...'));
|
|
@@ -1649,7 +3818,7 @@ async function handleTemplate() {
|
|
|
1649
3818
|
console.log(chalk.green(' Git initialized.'));
|
|
1650
3819
|
}
|
|
1651
3820
|
// 4. Run codeyam init
|
|
1652
|
-
console.log(chalk.bold('
|
|
3821
|
+
console.log(chalk.bold('Initializing project...'));
|
|
1653
3822
|
await initCommand.handler({
|
|
1654
3823
|
force: true,
|
|
1655
3824
|
'keep-server': true,
|
|
@@ -1674,6 +3843,22 @@ async function handleTemplate() {
|
|
|
1674
3843
|
// Config parse error is non-fatal
|
|
1675
3844
|
}
|
|
1676
3845
|
}
|
|
3846
|
+
// 5b. Mark the project as template-scaffolded so migration detection
|
|
3847
|
+
// doesn't treat it as a pre-existing project that needs migration.
|
|
3848
|
+
const now = new Date().toISOString();
|
|
3849
|
+
const existingState = readState(root);
|
|
3850
|
+
writeState(root, {
|
|
3851
|
+
feature: '',
|
|
3852
|
+
step: 0,
|
|
3853
|
+
label: '',
|
|
3854
|
+
startedAt: now,
|
|
3855
|
+
featureStartedAt: now,
|
|
3856
|
+
...existingState,
|
|
3857
|
+
scaffolded: true,
|
|
3858
|
+
});
|
|
3859
|
+
// 5c. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
3860
|
+
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
3861
|
+
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
1677
3862
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
1678
3863
|
console.log(chalk.bold('Refreshing editor...'));
|
|
1679
3864
|
try {
|
|
@@ -1692,17 +3877,14 @@ async function handleTemplate() {
|
|
|
1692
3877
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
1693
3878
|
/**
|
|
1694
3879
|
* `codeyam editor sync`
|
|
1695
|
-
* Import scenarios from scenarios
|
|
3880
|
+
* Import scenarios from editor-scenarios/ files into the local database.
|
|
3881
|
+
* Falls back to legacy scenarios-manifest.json if no _metadata files found.
|
|
1696
3882
|
*/
|
|
1697
3883
|
async function handleSync() {
|
|
1698
3884
|
const root = getProjectRoot();
|
|
1699
|
-
const
|
|
1700
|
-
if (
|
|
1701
|
-
console.log(chalk.yellow('No
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
if (manifest.scenarios.length === 0) {
|
|
1705
|
-
console.log(chalk.dim('Manifest is empty. Nothing to sync.'));
|
|
3885
|
+
const entries = scanScenarioFiles(root);
|
|
3886
|
+
if (entries.length === 0) {
|
|
3887
|
+
console.log(chalk.yellow('No scenario files with metadata found. Nothing to sync.'));
|
|
1706
3888
|
return;
|
|
1707
3889
|
}
|
|
1708
3890
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
@@ -1715,7 +3897,7 @@ async function handleSync() {
|
|
|
1715
3897
|
// fall through
|
|
1716
3898
|
}
|
|
1717
3899
|
if (!projectSlug) {
|
|
1718
|
-
console.error(chalk.red('Error: No project slug found. Run codeyam
|
|
3900
|
+
console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
|
|
1719
3901
|
process.exit(1);
|
|
1720
3902
|
}
|
|
1721
3903
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -1726,13 +3908,12 @@ async function handleSync() {
|
|
|
1726
3908
|
const { project } = await requireBranchAndProject(projectSlug);
|
|
1727
3909
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
1728
3910
|
const db = getDatabase();
|
|
1729
|
-
// Fetch existing editor scenarios
|
|
1730
3911
|
const existingRows = await db
|
|
1731
3912
|
.selectFrom('editor_scenarios')
|
|
1732
3913
|
.select(['id', 'updated_at'])
|
|
1733
3914
|
.where('project_id', '=', project.id)
|
|
1734
3915
|
.execute();
|
|
1735
|
-
const result = await
|
|
3916
|
+
const result = await syncScenarioFilesToDatabase(root, project.id, existingRows, async (row) => {
|
|
1736
3917
|
await db
|
|
1737
3918
|
.insertInto('editor_scenarios')
|
|
1738
3919
|
.values(row)
|
|
@@ -1757,14 +3938,45 @@ async function handleSync() {
|
|
|
1757
3938
|
parts.push(`${result.skipped} unchanged`);
|
|
1758
3939
|
console.log(chalk.green(`Synced scenarios: ${parts.join(', ')}`));
|
|
1759
3940
|
}
|
|
3941
|
+
// Migrate legacy scenario formats after sync, then re-sync if anything was fixed
|
|
3942
|
+
try {
|
|
3943
|
+
const migrateResult = migrateScenarioFormats(root);
|
|
3944
|
+
if (migrateResult.fixed > 0) {
|
|
3945
|
+
console.log(chalk.green(`Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
3946
|
+
// Re-sync so the DB reflects the corrected file metadata
|
|
3947
|
+
const refreshedRows = await db
|
|
3948
|
+
.selectFrom('editor_scenarios')
|
|
3949
|
+
.select(['id', 'updated_at'])
|
|
3950
|
+
.where('project_id', '=', project.id)
|
|
3951
|
+
.execute();
|
|
3952
|
+
await syncScenarioFilesToDatabase(root, project.id, refreshedRows, async (row) => {
|
|
3953
|
+
await db
|
|
3954
|
+
.insertInto('editor_scenarios')
|
|
3955
|
+
.values(row)
|
|
3956
|
+
.execute();
|
|
3957
|
+
}, async (id, row) => {
|
|
3958
|
+
await db
|
|
3959
|
+
.updateTable('editor_scenarios')
|
|
3960
|
+
.set(row)
|
|
3961
|
+
.where('id', '=', id)
|
|
3962
|
+
.execute();
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
catch {
|
|
3967
|
+
// Non-fatal
|
|
3968
|
+
}
|
|
1760
3969
|
}
|
|
1761
3970
|
// ─── Verify Images subcommand ─────────────────────────────────────────
|
|
1762
3971
|
async function handleVerifyImages(jsonArg) {
|
|
1763
3972
|
const port = getServerPort();
|
|
1764
|
-
const {
|
|
3973
|
+
const { parseVerifyImagesInput, extractImageUrls, resolveImageUrl, verifyImageUrls, buildVerifyImagesReport, extractImageUrlsFromScenarioFiles, } = await import('../utils/editorImageVerifier.js');
|
|
1765
3974
|
let paths;
|
|
3975
|
+
let explicitImageUrls;
|
|
1766
3976
|
try {
|
|
1767
|
-
|
|
3977
|
+
const input = parseVerifyImagesInput(jsonArg);
|
|
3978
|
+
paths = input.paths;
|
|
3979
|
+
explicitImageUrls = input.imageUrls;
|
|
1768
3980
|
}
|
|
1769
3981
|
catch {
|
|
1770
3982
|
console.error(chalk.red('Error: Invalid JSON argument'));
|
|
@@ -1802,12 +4014,23 @@ async function handleVerifyImages(jsonArg) {
|
|
|
1802
4014
|
console.error(chalk.red(` ✗ ${pagePath} — ${err.message}`));
|
|
1803
4015
|
}
|
|
1804
4016
|
}
|
|
4017
|
+
// Add explicitly-provided image URLs (Claude passes these directly)
|
|
4018
|
+
for (const url of explicitImageUrls) {
|
|
4019
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
4020
|
+
}
|
|
4021
|
+
// Also scan scenario mock data files for image URLs (client-rendered pages)
|
|
4022
|
+
const scenariosDir = path.join(getProjectRoot(), '.codeyam', 'editor-scenarios');
|
|
4023
|
+
const { urls: scenarioUrls, filesScanned: scenarioFilesScanned } = extractImageUrlsFromScenarioFiles(scenariosDir);
|
|
4024
|
+
for (const url of scenarioUrls) {
|
|
4025
|
+
allUrls.add(resolveImageUrl(url, devServerUrl));
|
|
4026
|
+
}
|
|
1805
4027
|
const urlList = [...allUrls];
|
|
1806
4028
|
const results = urlList.length > 0 ? await verifyImageUrls(urlList) : [];
|
|
1807
4029
|
const report = buildVerifyImagesReport({
|
|
1808
4030
|
pagesChecked,
|
|
1809
4031
|
imageUrls: urlList,
|
|
1810
4032
|
results,
|
|
4033
|
+
scenarioFilesScanned,
|
|
1811
4034
|
});
|
|
1812
4035
|
console.log(JSON.stringify(report, null, 2));
|
|
1813
4036
|
if (report.failures.length > 0) {
|
|
@@ -1858,7 +4081,7 @@ function handleEditorDebug(args) {
|
|
|
1858
4081
|
scenarios.push({
|
|
1859
4082
|
id: 'overview-with-state',
|
|
1860
4083
|
title: 'Cycle overview (project, with state)',
|
|
1861
|
-
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(
|
|
4084
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
|
|
1862
4085
|
});
|
|
1863
4086
|
}
|
|
1864
4087
|
const stepFns = {
|
|
@@ -1875,8 +4098,13 @@ function handleEditorDebug(args) {
|
|
|
1875
4098
|
11: printStep11,
|
|
1876
4099
|
12: printStep12,
|
|
1877
4100
|
13: printStep13,
|
|
4101
|
+
14: printStep14,
|
|
4102
|
+
15: printStep15,
|
|
4103
|
+
16: printStep16,
|
|
4104
|
+
17: printStep17,
|
|
4105
|
+
18: printStep18,
|
|
1878
4106
|
};
|
|
1879
|
-
for (let step = 1; step <=
|
|
4107
|
+
for (let step = 1; step <= 18; step++) {
|
|
1880
4108
|
const stepId = `step-${step}`;
|
|
1881
4109
|
if (!wants(stepId))
|
|
1882
4110
|
continue;
|
|
@@ -1896,7 +4124,7 @@ function handleEditorDebug(args) {
|
|
|
1896
4124
|
if (step === 2) {
|
|
1897
4125
|
scenarios.push({
|
|
1898
4126
|
id: 'step-2-scaffold',
|
|
1899
|
-
title: 'Step 2 (
|
|
4127
|
+
title: 'Step 2 (Prepare) — scaffold flow (no project)',
|
|
1900
4128
|
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
|
|
1901
4129
|
});
|
|
1902
4130
|
}
|
|
@@ -1955,8 +4183,8 @@ const editorCommand = {
|
|
|
1955
4183
|
describe: 'Editor mode guided workflow',
|
|
1956
4184
|
builder: (yargs) => {
|
|
1957
4185
|
const stepDescription = IS_INTERNAL_BUILD
|
|
1958
|
-
? 'Step number (1-
|
|
1959
|
-
: 'Step number (1-
|
|
4186
|
+
? 'Step number (1-18) 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)'
|
|
4187
|
+
: 'Step number (1-18) 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)';
|
|
1960
4188
|
let builder = yargs
|
|
1961
4189
|
.positional('step', {
|
|
1962
4190
|
type: 'string',
|
|
@@ -1969,6 +4197,14 @@ const editorCommand = {
|
|
|
1969
4197
|
.option('feature', {
|
|
1970
4198
|
type: 'string',
|
|
1971
4199
|
describe: 'Feature name (required for step 2)',
|
|
4200
|
+
})
|
|
4201
|
+
.option('app-formats', {
|
|
4202
|
+
type: 'string',
|
|
4203
|
+
describe: 'Comma-separated app formats (mobile-responsive-web-app, desktop-app, mobile-app)',
|
|
4204
|
+
})
|
|
4205
|
+
.option('tech-stack', {
|
|
4206
|
+
type: 'string',
|
|
4207
|
+
describe: 'Selected tech stack ID (e.g., nextjs-prisma-sqlite)',
|
|
1972
4208
|
})
|
|
1973
4209
|
.option('prompt', {
|
|
1974
4210
|
type: 'string',
|
|
@@ -1984,7 +4220,7 @@ const editorCommand = {
|
|
|
1984
4220
|
builder = builder
|
|
1985
4221
|
.option('target', {
|
|
1986
4222
|
type: 'string',
|
|
1987
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
4223
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
|
|
1988
4224
|
})
|
|
1989
4225
|
.option('resume', {
|
|
1990
4226
|
type: 'boolean',
|
|
@@ -2000,7 +4236,9 @@ const editorCommand = {
|
|
|
2000
4236
|
describe: 'Debug: output directory for the bundle',
|
|
2001
4237
|
});
|
|
2002
4238
|
}
|
|
2003
|
-
|
|
4239
|
+
// Allow extra positional args for subcommands like `isolate A B C`
|
|
4240
|
+
// without yargs rejecting them as unknown.
|
|
4241
|
+
return builder.strict(false);
|
|
2004
4242
|
},
|
|
2005
4243
|
handler: async (argv) => {
|
|
2006
4244
|
const root = getProjectRoot();
|
|
@@ -2018,7 +4256,19 @@ const editorCommand = {
|
|
|
2018
4256
|
process.exit(1);
|
|
2019
4257
|
}
|
|
2020
4258
|
if (result.data != null) {
|
|
2021
|
-
|
|
4259
|
+
const summary = formatApiSubcommandResult(argv.step, result.data);
|
|
4260
|
+
if (summary) {
|
|
4261
|
+
console.log(summary);
|
|
4262
|
+
}
|
|
4263
|
+
else {
|
|
4264
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
// After a successful commit, remind Claude to continue to step 17
|
|
4268
|
+
if (argv.step === 'commit' && result.ok) {
|
|
4269
|
+
console.log();
|
|
4270
|
+
console.log(chalk.green('Commit done. Now run: ') +
|
|
4271
|
+
chalk.bold('codeyam editor 17'));
|
|
2022
4272
|
}
|
|
2023
4273
|
}
|
|
2024
4274
|
catch (err) {
|
|
@@ -2033,6 +4283,11 @@ const editorCommand = {
|
|
|
2033
4283
|
await handleRegister(argv.json || '');
|
|
2034
4284
|
return;
|
|
2035
4285
|
}
|
|
4286
|
+
// Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
|
|
4287
|
+
if (argv.step === 'glossary-add') {
|
|
4288
|
+
await handleGlossaryAdd(argv.json || '');
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
2036
4291
|
// Subcommand: codeyam editor analyze-imports
|
|
2037
4292
|
if (argv.step === 'analyze-imports') {
|
|
2038
4293
|
await handleAnalyzeImports();
|
|
@@ -2053,6 +4308,16 @@ const editorCommand = {
|
|
|
2053
4308
|
await handleScenarios();
|
|
2054
4309
|
return;
|
|
2055
4310
|
}
|
|
4311
|
+
// Subcommand: codeyam editor scenario-coverage
|
|
4312
|
+
if (argv.step === 'scenario-coverage') {
|
|
4313
|
+
await handleScenarioCoverage();
|
|
4314
|
+
return;
|
|
4315
|
+
}
|
|
4316
|
+
// Subcommand: codeyam editor recapture-stale
|
|
4317
|
+
if (argv.step === 'recapture-stale') {
|
|
4318
|
+
await handleRecaptureStale();
|
|
4319
|
+
return;
|
|
4320
|
+
}
|
|
2056
4321
|
// Subcommand: codeyam editor change <feature>
|
|
2057
4322
|
if (argv.step === 'change') {
|
|
2058
4323
|
handleChange(argv.json || '');
|
|
@@ -2063,6 +4328,24 @@ const editorCommand = {
|
|
|
2063
4328
|
await handleVerifyImages(argv.json || '');
|
|
2064
4329
|
return;
|
|
2065
4330
|
}
|
|
4331
|
+
// Subcommand: codeyam editor validate-seed '{"products":[...]}'
|
|
4332
|
+
if (argv.step === 'validate-seed') {
|
|
4333
|
+
await handleValidateSeed(argv.json || '');
|
|
4334
|
+
return;
|
|
4335
|
+
}
|
|
4336
|
+
// Subcommand: codeyam editor delete <scenarioId>
|
|
4337
|
+
if (argv.step === 'delete') {
|
|
4338
|
+
await handleDelete(argv.json || '');
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
4342
|
+
// Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
4343
|
+
if (argv.step === 'isolate') {
|
|
4344
|
+
const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
|
|
4345
|
+
const names = parseIsolateArgs(argv.json, argv._);
|
|
4346
|
+
handleIsolate(names);
|
|
4347
|
+
return;
|
|
4348
|
+
}
|
|
2066
4349
|
// Subcommand: codeyam editor template — scaffold project from template
|
|
2067
4350
|
if (argv.step === 'template') {
|
|
2068
4351
|
await handleTemplate();
|
|
@@ -2082,6 +4365,11 @@ const editorCommand = {
|
|
|
2082
4365
|
await handleEditorDebug(argv);
|
|
2083
4366
|
return;
|
|
2084
4367
|
}
|
|
4368
|
+
// Subcommand: codeyam editor migrate [subArg]
|
|
4369
|
+
if (argv.step === 'migrate') {
|
|
4370
|
+
handleMigrateCommand(root, argv.json || undefined);
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
2085
4373
|
// Subcommand: codeyam editor steps — show setup or cycle overview
|
|
2086
4374
|
if (argv.step === 'steps') {
|
|
2087
4375
|
if (!hasProject(root)) {
|
|
@@ -2089,18 +4377,13 @@ const editorCommand = {
|
|
|
2089
4377
|
}
|
|
2090
4378
|
else {
|
|
2091
4379
|
const state = readState(root);
|
|
2092
|
-
// Clear prompt file when feature is done (step 13) so the hook
|
|
2093
|
-
// can capture the next feature request from the user.
|
|
2094
|
-
if (state?.step === 13) {
|
|
2095
|
-
clearEditorUserPrompt(root);
|
|
2096
|
-
}
|
|
2097
4380
|
printCycleOverview(root, state);
|
|
2098
4381
|
}
|
|
2099
4382
|
return;
|
|
2100
4383
|
}
|
|
2101
4384
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
2102
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
2103
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
4385
|
+
if (step != null && (isNaN(step) || step < 1 || step > 18)) {
|
|
4386
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
|
|
2104
4387
|
process.exit(1);
|
|
2105
4388
|
}
|
|
2106
4389
|
if (step == null) {
|
|
@@ -2124,7 +4407,7 @@ const editorCommand = {
|
|
|
2124
4407
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
2125
4408
|
const { projectSlug } = config;
|
|
2126
4409
|
if (!projectSlug) {
|
|
2127
|
-
errorLog('Missing project slug. Try reinitializing with: codeyam
|
|
4410
|
+
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
2128
4411
|
return;
|
|
2129
4412
|
}
|
|
2130
4413
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -2133,9 +4416,45 @@ const editorCommand = {
|
|
|
2133
4416
|
return;
|
|
2134
4417
|
}
|
|
2135
4418
|
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
2136
|
-
//
|
|
2137
|
-
|
|
2138
|
-
|
|
4419
|
+
// Backfill _metadata into existing scenario files that predate the embedded metadata feature.
|
|
4420
|
+
// This reads metadata from the DB and writes it into files that don't have _metadata yet.
|
|
4421
|
+
try {
|
|
4422
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4423
|
+
const db = getDatabase();
|
|
4424
|
+
const dbScenarios = await db
|
|
4425
|
+
.selectFrom('editor_scenarios')
|
|
4426
|
+
.select([
|
|
4427
|
+
'id',
|
|
4428
|
+
'name',
|
|
4429
|
+
'description',
|
|
4430
|
+
'component_name',
|
|
4431
|
+
'component_path',
|
|
4432
|
+
'url',
|
|
4433
|
+
'type',
|
|
4434
|
+
'screenshot_path',
|
|
4435
|
+
'viewport_width',
|
|
4436
|
+
'viewport_height',
|
|
4437
|
+
'dimensions',
|
|
4438
|
+
'screenshot_paths',
|
|
4439
|
+
'page_file_path',
|
|
4440
|
+
'created_at',
|
|
4441
|
+
'updated_at',
|
|
4442
|
+
])
|
|
4443
|
+
.where('project_id', '=', project.id)
|
|
4444
|
+
.execute();
|
|
4445
|
+
if (dbScenarios.length > 0) {
|
|
4446
|
+
const backfilled = backfillScenarioMetadata(projectRoot, dbScenarios);
|
|
4447
|
+
if (backfilled > 0) {
|
|
4448
|
+
console.log(chalk.green(` Backfilled metadata into ${backfilled} scenario file${backfilled > 1 ? 's' : ''}`));
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
catch {
|
|
4453
|
+
// Non-fatal — backfill is best-effort
|
|
4454
|
+
}
|
|
4455
|
+
// Auto-sync scenario files (with _metadata) to database
|
|
4456
|
+
const scenarioEntries = scanScenarioFiles(projectRoot);
|
|
4457
|
+
if (scenarioEntries.length > 0) {
|
|
2139
4458
|
try {
|
|
2140
4459
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2141
4460
|
const db = getDatabase();
|
|
@@ -2144,7 +4463,7 @@ const editorCommand = {
|
|
|
2144
4463
|
.select(['id', 'updated_at'])
|
|
2145
4464
|
.where('project_id', '=', project.id)
|
|
2146
4465
|
.execute();
|
|
2147
|
-
const syncResult = await
|
|
4466
|
+
const syncResult = await syncScenarioFilesToDatabase(projectRoot, project.id, existingRows, async (row) => {
|
|
2148
4467
|
await db
|
|
2149
4468
|
.insertInto('editor_scenarios')
|
|
2150
4469
|
.values(row)
|
|
@@ -2162,13 +4481,85 @@ const editorCommand = {
|
|
|
2162
4481
|
parts.push(`${syncResult.inserted} imported`);
|
|
2163
4482
|
if (syncResult.updated > 0)
|
|
2164
4483
|
parts.push(`${syncResult.updated} updated`);
|
|
2165
|
-
console.log(chalk.green(` Synced scenarios from
|
|
4484
|
+
console.log(chalk.green(` Synced scenarios from files: ${parts.join(', ')}`));
|
|
2166
4485
|
}
|
|
2167
4486
|
}
|
|
2168
4487
|
catch {
|
|
2169
4488
|
// Non-fatal — sync failure shouldn't block editor startup
|
|
2170
4489
|
}
|
|
2171
4490
|
}
|
|
4491
|
+
// Migrate legacy scenario formats: resolve null viewports, populate
|
|
4492
|
+
// dimensions arrays, and build screenshotPaths maps from single values.
|
|
4493
|
+
// If files were fixed, re-sync to update the database with corrected values.
|
|
4494
|
+
try {
|
|
4495
|
+
const migrateResult = migrateScenarioFormats(projectRoot);
|
|
4496
|
+
if (migrateResult.fixed > 0) {
|
|
4497
|
+
console.log(chalk.green(` Migrated ${migrateResult.fixed} scenario file${migrateResult.fixed > 1 ? 's' : ''} to current format`));
|
|
4498
|
+
// Re-sync so the DB reflects the fixed file metadata
|
|
4499
|
+
try {
|
|
4500
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4501
|
+
const db = getDatabase();
|
|
4502
|
+
const rows = await db
|
|
4503
|
+
.selectFrom('editor_scenarios')
|
|
4504
|
+
.select(['id', 'updated_at'])
|
|
4505
|
+
.where('project_id', '=', project.id)
|
|
4506
|
+
.execute();
|
|
4507
|
+
await syncScenarioFilesToDatabase(projectRoot, project.id, rows, async (row) => {
|
|
4508
|
+
await db
|
|
4509
|
+
.insertInto('editor_scenarios')
|
|
4510
|
+
.values(row)
|
|
4511
|
+
.execute();
|
|
4512
|
+
}, async (id, row) => {
|
|
4513
|
+
await db
|
|
4514
|
+
.updateTable('editor_scenarios')
|
|
4515
|
+
.set(row)
|
|
4516
|
+
.where('id', '=', id)
|
|
4517
|
+
.execute();
|
|
4518
|
+
});
|
|
4519
|
+
}
|
|
4520
|
+
catch {
|
|
4521
|
+
// Non-fatal — DB re-sync failure shouldn't block startup
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
catch {
|
|
4526
|
+
// Non-fatal — migration failure shouldn't block editor startup
|
|
4527
|
+
}
|
|
4528
|
+
// Auto-seed on fresh clone: if no scenario has ever been activated
|
|
4529
|
+
// (active-scenario.json doesn't exist), seed the application database
|
|
4530
|
+
// with the first application scenario that has seed data.
|
|
4531
|
+
const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
|
|
4532
|
+
if (!fs.existsSync(activeScenarioPath)) {
|
|
4533
|
+
try {
|
|
4534
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4535
|
+
const seedDb = getDb();
|
|
4536
|
+
const appScenario = await seedDb
|
|
4537
|
+
.selectFrom('editor_scenarios')
|
|
4538
|
+
.select(['id', 'name', 'type'])
|
|
4539
|
+
.where('project_id', '=', project.id)
|
|
4540
|
+
.where('type', '=', 'application')
|
|
4541
|
+
.orderBy('created_at', 'asc')
|
|
4542
|
+
.executeTakeFirst();
|
|
4543
|
+
if (appScenario) {
|
|
4544
|
+
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4545
|
+
if (fs.existsSync(seedFile)) {
|
|
4546
|
+
const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
|
|
4547
|
+
const seedResult = await switchActiveScenario({
|
|
4548
|
+
scenarioId: appScenario.id,
|
|
4549
|
+
scenarioName: appScenario.name || undefined,
|
|
4550
|
+
scenarioType: appScenario.type || undefined,
|
|
4551
|
+
projectRoot,
|
|
4552
|
+
});
|
|
4553
|
+
if (seedResult.seedResult?.success) {
|
|
4554
|
+
console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
catch {
|
|
4560
|
+
// Non-fatal — auto-seed is best-effort
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
2172
4563
|
// `codeyam editor` (no step) always implies editor mode.
|
|
2173
4564
|
// The empty-folder heuristic is no longer needed here — running this
|
|
2174
4565
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -2207,38 +4598,177 @@ const editorCommand = {
|
|
|
2207
4598
|
editorMode,
|
|
2208
4599
|
});
|
|
2209
4600
|
// Auto-finalize analyzer so codeyam analyze works
|
|
2210
|
-
if (editorMode
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
});
|
|
2224
|
-
execSync('npm run build', {
|
|
2225
|
-
cwd: templatePath,
|
|
2226
|
-
stdio: 'pipe',
|
|
2227
|
-
});
|
|
2228
|
-
fs.writeFileSync(path.join(templatePath, '.finalized'), new Date().toISOString());
|
|
4601
|
+
if (editorMode) {
|
|
4602
|
+
const stepLabels = {
|
|
4603
|
+
'npm-install': 'Installing simulation dependencies...',
|
|
4604
|
+
'playwright-install': 'Installing browser (Chromium)...',
|
|
4605
|
+
build: 'Building simulation engine...',
|
|
4606
|
+
};
|
|
4607
|
+
const simProgress = new ProgressReporter();
|
|
4608
|
+
const finalization = ensureAnalyzerFinalized({
|
|
4609
|
+
onProgress: (step) => simProgress.start(stepLabels[step]),
|
|
4610
|
+
});
|
|
4611
|
+
if (finalization.errors.length > 0) {
|
|
4612
|
+
for (const err of finalization.errors) {
|
|
4613
|
+
simProgress.warn(`${stepLabels[err.step].replace('...', '')} failed: ${err.message}`);
|
|
2229
4614
|
}
|
|
2230
4615
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
4616
|
+
else if (finalization.needed) {
|
|
4617
|
+
simProgress.succeed('Simulation engine ready');
|
|
2233
4618
|
}
|
|
2234
4619
|
}
|
|
2235
|
-
// Start background server
|
|
4620
|
+
// Start background server (handles killing existing servers internally)
|
|
4621
|
+
const editorPort = argv.port || 3111;
|
|
2236
4622
|
const { url } = await startBackgroundServer({
|
|
2237
|
-
port:
|
|
4623
|
+
port: editorPort,
|
|
2238
4624
|
rootPath: projectRoot,
|
|
2239
4625
|
project,
|
|
2240
4626
|
branch,
|
|
2241
4627
|
});
|
|
4628
|
+
// Build import graph if glossary exists but entities are missing.
|
|
4629
|
+
// Runs on first startup (no entities at all) AND when page files or
|
|
4630
|
+
// scenario component files lack entity coverage.
|
|
4631
|
+
const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
|
|
4632
|
+
if (fs.existsSync(glossaryPath)) {
|
|
4633
|
+
let needsAnalysis = false;
|
|
4634
|
+
const entities = await loadEntities({});
|
|
4635
|
+
if (!entities || entities.length === 0) {
|
|
4636
|
+
needsAnalysis = true;
|
|
4637
|
+
}
|
|
4638
|
+
else {
|
|
4639
|
+
const entityFilePaths = new Set(entities.map((e) => e.filePath));
|
|
4640
|
+
// Check if any page files are missing entities (Next.js apps)
|
|
4641
|
+
try {
|
|
4642
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
4643
|
+
const { allFiles } = scanPageFilePaths(projectRoot);
|
|
4644
|
+
const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
|
|
4645
|
+
const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
|
|
4646
|
+
if (missingPages.length > 0) {
|
|
4647
|
+
console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
|
|
4648
|
+
needsAnalysis = true;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
catch {
|
|
4652
|
+
// Non-fatal — page file check failed
|
|
4653
|
+
}
|
|
4654
|
+
// Check if any scenario files (component_path or page_file_path)
|
|
4655
|
+
// are missing entities — covers non-Next.js apps
|
|
4656
|
+
if (!needsAnalysis) {
|
|
4657
|
+
try {
|
|
4658
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4659
|
+
const db = getDatabase();
|
|
4660
|
+
const scenarioFiles = await db
|
|
4661
|
+
.selectFrom('editor_scenarios')
|
|
4662
|
+
.select(['component_path', 'page_file_path'])
|
|
4663
|
+
.where('project_id', '=', project.id)
|
|
4664
|
+
.distinct()
|
|
4665
|
+
.execute();
|
|
4666
|
+
const missingCount = scenarioFiles.filter((row) => {
|
|
4667
|
+
const cp = row.component_path;
|
|
4668
|
+
const pfp = row.page_file_path;
|
|
4669
|
+
return ((cp && !entityFilePaths.has(cp)) ||
|
|
4670
|
+
(pfp && !entityFilePaths.has(pfp)));
|
|
4671
|
+
}).length;
|
|
4672
|
+
if (missingCount > 0) {
|
|
4673
|
+
console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
|
|
4674
|
+
needsAnalysis = true;
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
catch {
|
|
4678
|
+
// Non-fatal
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
// Check if any scenarios have null entity_sha with file paths — heal on startup
|
|
4682
|
+
if (!needsAnalysis) {
|
|
4683
|
+
try {
|
|
4684
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4685
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4686
|
+
const db = getDatabase();
|
|
4687
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4688
|
+
if (backfillCount > 0) {
|
|
4689
|
+
console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
|
|
4690
|
+
needsAnalysis = true;
|
|
4691
|
+
}
|
|
4692
|
+
}
|
|
4693
|
+
catch {
|
|
4694
|
+
// Non-fatal
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
if (needsAnalysis) {
|
|
4699
|
+
try {
|
|
4700
|
+
await handleAnalyzeImports({ silent: true });
|
|
4701
|
+
}
|
|
4702
|
+
catch {
|
|
4703
|
+
// Non-fatal
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
// Backfill page_file_path for application scenarios that have a URL but
|
|
4708
|
+
// no page_file_path. This resolves the file from the URL using Next.js
|
|
4709
|
+
// routing conventions, enabling syncScenarioEntityShas to link them to
|
|
4710
|
+
// entities. Covers fresh clones where JSON files lack pageFilePath.
|
|
4711
|
+
try {
|
|
4712
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4713
|
+
const pfpDb = getDb();
|
|
4714
|
+
const unresolved = await pfpDb
|
|
4715
|
+
.selectFrom('editor_scenarios')
|
|
4716
|
+
.select(['id', 'url'])
|
|
4717
|
+
.where('project_id', '=', project.id)
|
|
4718
|
+
.where('component_name', 'is', null)
|
|
4719
|
+
.where('page_file_path', 'is', null)
|
|
4720
|
+
.where('url', 'is not', null)
|
|
4721
|
+
.execute();
|
|
4722
|
+
if (unresolved.length > 0) {
|
|
4723
|
+
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4724
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
4725
|
+
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4726
|
+
let pfpResolved = 0;
|
|
4727
|
+
for (const row of unresolved) {
|
|
4728
|
+
const r = row;
|
|
4729
|
+
if (!r.url)
|
|
4730
|
+
continue;
|
|
4731
|
+
const matched = matchUrlToPageFile(r.url, pfpFiles);
|
|
4732
|
+
if (matched) {
|
|
4733
|
+
await pfpDb
|
|
4734
|
+
.updateTable('editor_scenarios')
|
|
4735
|
+
.set({ page_file_path: matched })
|
|
4736
|
+
.where('id', '=', r.id)
|
|
4737
|
+
.execute();
|
|
4738
|
+
pfpResolved++;
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
if (pfpResolved > 0) {
|
|
4742
|
+
console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
catch {
|
|
4747
|
+
/* Non-fatal — page_file_path backfill from URL */
|
|
4748
|
+
}
|
|
4749
|
+
// Backfill entity_sha on scenarios that were synced before entities existed.
|
|
4750
|
+
// This runs independently of analyze-imports so fresh clones and file-synced
|
|
4751
|
+
// scenarios get linked to their entities even when auto-analyze doesn't trigger.
|
|
4752
|
+
try {
|
|
4753
|
+
const entities = await loadEntities({});
|
|
4754
|
+
if (entities && entities.length > 0) {
|
|
4755
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4756
|
+
const db = getDatabase();
|
|
4757
|
+
const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4758
|
+
sha: e.sha,
|
|
4759
|
+
name: e.name,
|
|
4760
|
+
filePath: e.filePath || '',
|
|
4761
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4762
|
+
e.metadata?.namedExport === false,
|
|
4763
|
+
})));
|
|
4764
|
+
if (result.updated > 0) {
|
|
4765
|
+
console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
catch {
|
|
4770
|
+
/* Non-fatal */
|
|
4771
|
+
}
|
|
2242
4772
|
console.log();
|
|
2243
4773
|
console.log(` Dashboard: ${url}`);
|
|
2244
4774
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -2252,7 +4782,9 @@ const editorCommand = {
|
|
|
2252
4782
|
: process.platform === 'win32'
|
|
2253
4783
|
? 'start ""'
|
|
2254
4784
|
: 'xdg-open';
|
|
2255
|
-
execSync(`${openCommand} "${url}/editor"`, {
|
|
4785
|
+
execSync(`${openCommand} "${url}/editor"`, {
|
|
4786
|
+
stdio: 'ignore',
|
|
4787
|
+
});
|
|
2256
4788
|
}
|
|
2257
4789
|
catch {
|
|
2258
4790
|
// Silently fail if open command doesn't work
|
|
@@ -2266,11 +4798,27 @@ const editorCommand = {
|
|
|
2266
4798
|
return;
|
|
2267
4799
|
}
|
|
2268
4800
|
const state = readState(root);
|
|
4801
|
+
// Validate step transition — prevent skipping steps.
|
|
4802
|
+
// Exception: step 2 with --feature is always allowed because step 1's
|
|
4803
|
+
// instructions explicitly tell Claude to run `codeyam editor 2 --feature "..."`.
|
|
4804
|
+
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4805
|
+
const skipValidation = step === 2 && argv.feature;
|
|
4806
|
+
if (!skipValidation) {
|
|
4807
|
+
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
4808
|
+
if (stepError) {
|
|
4809
|
+
console.error(chalk.red(`Error: ${stepError}`));
|
|
4810
|
+
process.exit(1);
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
2269
4813
|
switch (step) {
|
|
2270
4814
|
case 1: {
|
|
2271
4815
|
const feature = argv.feature || undefined;
|
|
4816
|
+
const appFormats = argv['app-formats']
|
|
4817
|
+
? argv['app-formats'].split(',').map((f) => f.trim())
|
|
4818
|
+
: undefined;
|
|
4819
|
+
const techStackId = argv['tech-stack'] || undefined;
|
|
2272
4820
|
const prompt = argv.prompt || undefined;
|
|
2273
|
-
printStep1(root, feature, prompt);
|
|
4821
|
+
printStep1(root, feature, { appFormats, techStackId }, prompt);
|
|
2274
4822
|
break;
|
|
2275
4823
|
}
|
|
2276
4824
|
case 2: {
|
|
@@ -2293,12 +4841,27 @@ const editorCommand = {
|
|
|
2293
4841
|
case 10:
|
|
2294
4842
|
case 11:
|
|
2295
4843
|
case 12:
|
|
2296
|
-
case 13:
|
|
4844
|
+
case 13:
|
|
4845
|
+
case 14:
|
|
4846
|
+
case 15:
|
|
4847
|
+
case 16:
|
|
4848
|
+
case 17:
|
|
4849
|
+
case 18: {
|
|
2297
4850
|
const feature = argv.feature || state?.feature;
|
|
2298
4851
|
if (!feature) {
|
|
2299
4852
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
2300
4853
|
process.exit(1);
|
|
2301
4854
|
}
|
|
4855
|
+
// Hard gate: steps 10+ require audit to have passed
|
|
4856
|
+
if (step >= 10) {
|
|
4857
|
+
const auditOk = await checkAuditGate();
|
|
4858
|
+
if (!auditOk) {
|
|
4859
|
+
// checkAuditGate() already printed specific failure details above
|
|
4860
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4861
|
+
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4862
|
+
process.exit(1);
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
2302
4865
|
const stepFns = {
|
|
2303
4866
|
3: printStep3,
|
|
2304
4867
|
4: printStep4,
|
|
@@ -2311,6 +4874,11 @@ const editorCommand = {
|
|
|
2311
4874
|
11: printStep11,
|
|
2312
4875
|
12: printStep12,
|
|
2313
4876
|
13: printStep13,
|
|
4877
|
+
14: printStep14,
|
|
4878
|
+
15: printStep15,
|
|
4879
|
+
16: printStep16,
|
|
4880
|
+
17: printStep17,
|
|
4881
|
+
18: printStep18,
|
|
2314
4882
|
};
|
|
2315
4883
|
stepFns[step](root, feature);
|
|
2316
4884
|
break;
|