@codeyam/codeyam-cli 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +8 -8
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +2 -2
- package/analyzer-template/packages/ai/package.json +1 -1
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +42 -16
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +3 -1
- 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 +44 -16
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/codeyam-cli/src/cli.js +9 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +11 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +1360 -201
- 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/telemetry.js +37 -0
- package/codeyam-cli/src/commands/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +893 -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__/editorDeleteScenario.test.js +100 -0
- package/codeyam-cli/src/utils/__tests__/editorDeleteScenario.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +76 -3
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +261 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +75 -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__/editorScenarios.test.js +441 -17
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js +143 -0
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapterPrismaValidation.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js +66 -0
- package/codeyam-cli/src/utils/__tests__/editorSessionFilter.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js +53 -0
- package/codeyam-cli/src/utils/__tests__/editorShouldRevalidate.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +67 -9
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js +118 -0
- package/codeyam-cli/src/utils/__tests__/routePatternMatching.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +40 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
- package/codeyam-cli/src/utils/analysisRunner.js +3 -1
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +145 -0
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorBroadcastViewport.js +26 -0
- package/codeyam-cli/src/utils/editorBroadcastViewport.js.map +1 -0
- package/codeyam-cli/src/utils/editorDeleteScenario.js +67 -0
- package/codeyam-cli/src/utils/editorDeleteScenario.js.map +1 -0
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/editorEntityHelpers.js +129 -0
- package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +40 -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/editorScenarios.js +163 -2
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +253 -4
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/editorShouldRevalidate.js +21 -0
- package/codeyam-cli/src/utils/editorShouldRevalidate.js.map +1 -0
- package/codeyam-cli/src/utils/entityChangeStatus.js +19 -2
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +7 -3
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/fileWatcher.js +38 -0
- package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
- package/codeyam-cli/src/utils/install-skills.js +9 -0
- package/codeyam-cli/src/utils/install-skills.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 +8 -9
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +18 -10
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/telemetry.js +106 -0
- package/codeyam-cli/src/utils/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js +35 -0
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +61 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.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 +18 -4
- package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-CzTDWkF2.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BcgbViKV.js → EntityItem-BFbq6iFk.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-CQgyEGV-.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CQIG2qda.js → EntityTypeIcon-B6OMi58N.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-DuYodzo1.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-CXo9EeCl.js +25 -0
- package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DYCNb2It.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-CZgY3sxX.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-BzHcG7SE.js → ReportIssueModal-CnYYwRDw.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-CDoF7ZpU.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-TSD3C211.js → ScenarioViewer-DrnfvaLL.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.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-DRKR9T0U.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{_index-DLxKhri3.js → _index-ClR-g3tY.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BcY3q6nt.js → activity.(_tab)-DTH6ydEA.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-Duc5hnl7.js → addon-web-links-74hnHF59.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bni3iiUj.js → agent-transcripts-B8CYhCO9.js} +3 -3
- 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-prompt-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-BYOypzCa.js → book-open-CLaoh4ac.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-C_Pmso5S.js → chevron-down-BZ2DZxbW.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-C4pqxYJB.js → chunk-JZWAC4HX-BBXArFPl.js} +13 -21
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-BVMi9VA5.js → circle-check-CT4unAk-.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-n2FB0_Sw.js → copy-zK0B6Nu-.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-CC6AbExI.js → createLucideIcon-DJB0YQJL.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-CkXFP_i-.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CjC3_6JI.js +58 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-DwCV5__E.js → entity._sha._-BqAN7hyG.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-CJX6kkkV.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BMvVHNXU.js → entity._sha_.edit._scenarioId-BhVjZhKg.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-DTvKq3TY.js → entry.client-_gzKltPN.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-CV_17tZS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/git-D-YXmMbR.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-yHOVb4rc.js → index-Blo6EK8G.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-10oVnAAH.js → index-BsX0F-9C.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-BcvgDzbZ.js → index-CCrgCshv.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-Byazq8Pv.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DaAZ_H2w.js → loader-circle-DVQ0oHR7.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-75b1b319.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-9gnxSZlb.js → memory-b-VmA2Vj.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{pause-f5-1lKBt.js → pause-DGcndCAa.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-DBjt6o04.js → root-F-k2uYj5.js} +15 -15
- package/codeyam-cli/src/webserver/build/client/assets/{search-Di64LWVb.js → search-C0Uw0bcK.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/settings-OoNgHIfW.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/simulations-Bcemfu8a.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-Br7MOqts.js → terminal-BgMmG7R9.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BLdiCuG-.js → triangle-alert-Cs87hJYK.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-BR3Rs7JY.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-C14nCb1q.js → useLastLogLine-BxxP_XF9.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useReportContext-BermyNU5.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/useToast-a_QN_W9_.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-lv2ooewK.js +13 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-DsZjKspK.js → index-Im3Smyei.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-BjuAFKGM.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-CNjF0B9B.js +551 -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 +112 -13
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/mockStateEvents.js +28 -0
- package/codeyam-cli/src/webserver/mockStateEvents.js.map +1 -0
- package/codeyam-cli/src/webserver/server.js +41 -0
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +74 -8
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/editor-step-hook.py +104 -20
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +42 -7
- package/codeyam-cli/templates/seed-adapters/supabase.ts +282 -0
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +62 -0
- package/package.json +2 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +44 -16
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/CopyButton-BPXZwM4t.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/EntityTypeBadge-g3saevPb.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/InlineSpinner-Bu6c6aDe.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/InteractivePreview-DYFW3lDD.js +0 -25
- package/codeyam-cli/src/webserver/build/client/assets/LibraryFunctionPreview-DLeucoVX.js +0 -3
- package/codeyam-cli/src/webserver/build/client/assets/SafeScreenshot-BED4B6sP.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bb5uFQ5V.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/TruncatedFilePath-C8OKAR5x.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/ViewportInspectBar-oAf2Kqsf.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/dev.empty-Ii3inc0_.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +0 -15
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.dev-BwKcai0j.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha_.create-scenario-p9hhkjJM.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/fileTableUtils-cPo8LiG3.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/files-BZrlFE1F.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/git-DdZcvjGh.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.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-76e7b62c.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/settings-0OrEMU6J.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/simulations-DWT-CvLy.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.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/init-DdqKD2p4.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +0 -444
|
@@ -16,10 +16,13 @@ import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
|
16
16
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
17
|
import initCommand from "./init.js";
|
|
18
18
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
-
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, } from "../utils/editorScenarios.js";
|
|
20
|
-
import { validateSeedData, detectSeedAdapter, } from "../utils/editorSeedAdapter.js";
|
|
19
|
+
import { clearEditorState, clearEditorUserPrompt, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
|
+
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
|
+
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
21
22
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
22
23
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
24
|
+
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
25
|
+
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
23
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
27
|
const __dirname = path.dirname(__filename);
|
|
25
28
|
const STEP_LABELS = {
|
|
@@ -40,6 +43,18 @@ const STEP_LABELS = {
|
|
|
40
43
|
15: 'Finalize',
|
|
41
44
|
16: 'Push',
|
|
42
45
|
};
|
|
46
|
+
const MIGRATION_STEP_LABELS = {
|
|
47
|
+
1: 'Survey',
|
|
48
|
+
2: 'App Scenarios',
|
|
49
|
+
3: 'Component Scenarios',
|
|
50
|
+
4: 'Preview',
|
|
51
|
+
5: 'Discuss',
|
|
52
|
+
6: 'Decompose',
|
|
53
|
+
7: 'Extract',
|
|
54
|
+
8: 'Recapture',
|
|
55
|
+
9: 'Journal',
|
|
56
|
+
10: 'Present',
|
|
57
|
+
};
|
|
43
58
|
/**
|
|
44
59
|
* Append a JSONL log entry to .codeyam/logs/editor-log.jsonl
|
|
45
60
|
*/
|
|
@@ -185,6 +200,146 @@ function checkbox(text) {
|
|
|
185
200
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
186
201
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
187
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
205
|
+
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
206
|
+
*/
|
|
207
|
+
function printAppScenarioInstructions(pageName, route) {
|
|
208
|
+
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
209
|
+
console.log(chalk.dim(' Review existing scenarios — reuse and update their data rather than'));
|
|
210
|
+
console.log(chalk.dim(' creating duplicates. Re-register with the same name to update a scenario.'));
|
|
211
|
+
console.log();
|
|
212
|
+
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
213
|
+
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
214
|
+
console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
|
|
215
|
+
console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
|
|
216
|
+
console.log();
|
|
217
|
+
checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
|
|
218
|
+
console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
|
|
219
|
+
console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
|
|
220
|
+
if (pageName) {
|
|
221
|
+
console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
225
|
+
}
|
|
226
|
+
console.log();
|
|
227
|
+
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
228
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
229
|
+
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
230
|
+
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
231
|
+
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
232
|
+
console.log();
|
|
233
|
+
checkbox('Choose the right data approach for scenarios:');
|
|
234
|
+
console.log(chalk.dim(' With seed adapter: add "seed":{...} to populate the database for each state'));
|
|
235
|
+
console.log(chalk.dim(' Without seed adapter: add "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
236
|
+
console.log(chalk.dim(' For external APIs (Stripe, weather, etc.): add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
237
|
+
checkbox('For large seed/mock data, write JSON to a temp file and use @ prefix:');
|
|
238
|
+
console.log(chalk.dim(' Write JSON to .codeyam/tmp/scenario.json then register with:'));
|
|
239
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
240
|
+
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
241
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
242
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
243
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Shared glossary instructions used by editor Step 6 and migration Step 8.
|
|
247
|
+
* Prints format, required fields, and example.
|
|
248
|
+
*/
|
|
249
|
+
function printGlossaryInstructions(feature) {
|
|
250
|
+
console.log(chalk.bold.yellow('IMPORTANT: glossary.json is a plain JSON array — NOT an object wrapper.'));
|
|
251
|
+
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
252
|
+
checkbox('Add an entry for each new function/component extracted');
|
|
253
|
+
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
254
|
+
checkbox('For each function with a test file, set `testFile` to the relative path');
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(chalk.bold('File format:'));
|
|
257
|
+
console.log(chalk.dim(' ['));
|
|
258
|
+
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
259
|
+
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
260
|
+
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
261
|
+
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
262
|
+
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
263
|
+
console.log(chalk.dim(` "feature": "${feature || '...'}", "createdAt": "..." }`));
|
|
264
|
+
console.log(chalk.dim(' ]'));
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Shared extraction plan instructions used by editor Step 4 and migration Step 6.
|
|
268
|
+
* Prints THE RULE, extractable categories, plan format requirements.
|
|
269
|
+
*/
|
|
270
|
+
function printExtractionPlanInstructions() {
|
|
271
|
+
console.log(chalk.bold.red('THE RULE: No direct JSX in page files.'));
|
|
272
|
+
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
273
|
+
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
274
|
+
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
275
|
+
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
276
|
+
console.log();
|
|
277
|
+
console.log(chalk.bold('Checklist:'));
|
|
278
|
+
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
279
|
+
checkbox('Read EVERY file used by this page/feature');
|
|
280
|
+
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
281
|
+
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
282
|
+
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
283
|
+
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
284
|
+
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
285
|
+
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
286
|
+
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
287
|
+
console.log();
|
|
288
|
+
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
289
|
+
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
290
|
+
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
291
|
+
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
294
|
+
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
295
|
+
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
296
|
+
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
297
|
+
console.log();
|
|
298
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
302
|
+
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
303
|
+
*/
|
|
304
|
+
function printComponentCaptureInstructions() {
|
|
305
|
+
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
306
|
+
console.log(chalk.dim(' This creates app/codeyam-isolate/layout.tsx (with production notFound() guard) and'));
|
|
307
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
308
|
+
checkbox('For each visual component:');
|
|
309
|
+
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
310
|
+
console.log(chalk.dim(' — Props/interface'));
|
|
311
|
+
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
312
|
+
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
313
|
+
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
314
|
+
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
315
|
+
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
316
|
+
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
317
|
+
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
318
|
+
console.log(chalk.dim(' Remix: app/routes/codeyam-isolate.ComponentName.tsx → /codeyam-isolate/ComponentName'));
|
|
319
|
+
console.log(chalk.dim(' Next.js: app/codeyam-isolate/ComponentName/page.tsx → /codeyam-isolate/ComponentName'));
|
|
320
|
+
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
321
|
+
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
322
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
323
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
|
|
324
|
+
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
325
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
326
|
+
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
327
|
+
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
328
|
+
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
329
|
+
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
330
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
331
|
+
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
332
|
+
console.log(chalk.dim(` "url":"/codeyam-isolate/ComponentName?s=Scenario",`));
|
|
333
|
+
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
334
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/codeyam-isolate/... or /isolated-components/...).'));
|
|
335
|
+
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
336
|
+
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
337
|
+
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
338
|
+
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
339
|
+
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
340
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
341
|
+
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
342
|
+
}
|
|
188
343
|
/**
|
|
189
344
|
* Print a section header.
|
|
190
345
|
*/
|
|
@@ -570,6 +725,42 @@ function printSetup(root) {
|
|
|
570
725
|
// ─── Cycle overview (no args, has project) ────────────────────────────
|
|
571
726
|
function printCycleOverview(root, state) {
|
|
572
727
|
logEvent(root, 'overview', state ? { feature: state.feature, step: state.step } : {});
|
|
728
|
+
// Detect active migration (uses both editor-step.json and migration-state.json)
|
|
729
|
+
const migState = readMigrationState(root);
|
|
730
|
+
const resumeInfo = getMigrationResumeInfo(state
|
|
731
|
+
? { step: state.step, label: state.label, migration: state.migration }
|
|
732
|
+
: null, migState);
|
|
733
|
+
if (resumeInfo.mode === 'survey') {
|
|
734
|
+
console.log();
|
|
735
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
736
|
+
console.log();
|
|
737
|
+
console.log(chalk.yellow('Migration survey in progress — Claude needs to explore the project.'));
|
|
738
|
+
console.log();
|
|
739
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
740
|
+
console.log();
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (resumeInfo.mode === 'page-in-progress') {
|
|
744
|
+
console.log();
|
|
745
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Project Migration ━━━'));
|
|
746
|
+
console.log();
|
|
747
|
+
console.log(chalk.yellow(`Migration: Page ${resumeInfo.pageIndex + 1}/${resumeInfo.totalPages} (${resumeInfo.pageName})` +
|
|
748
|
+
(resumeInfo.step
|
|
749
|
+
? `, Step ${resumeInfo.step} (${resumeInfo.stepLabel})`
|
|
750
|
+
: '')));
|
|
751
|
+
console.log();
|
|
752
|
+
console.log(chalk.green('Continue with: ') + chalk.bold(resumeInfo.resumeCommand));
|
|
753
|
+
console.log();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (resumeInfo.mode === 'complete') {
|
|
757
|
+
console.log();
|
|
758
|
+
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
759
|
+
console.log();
|
|
760
|
+
console.log(chalk.green('Migration complete! Start building features:'));
|
|
761
|
+
console.log();
|
|
762
|
+
// Fall through to normal cycle display
|
|
763
|
+
}
|
|
573
764
|
console.log();
|
|
574
765
|
console.log(chalk.bold.cyan('━━━ Editor Mode: Feature Cycle ━━━'));
|
|
575
766
|
console.log();
|
|
@@ -748,13 +939,29 @@ function printStep2(root, feature) {
|
|
|
748
939
|
console.log();
|
|
749
940
|
console.log(chalk.bold('Build the feature:'));
|
|
750
941
|
}
|
|
942
|
+
else {
|
|
943
|
+
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
944
|
+
checkbox('List existing scenarios: `codeyam editor scenarios`');
|
|
945
|
+
console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
|
|
946
|
+
console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
|
|
947
|
+
checkbox('Pick the scenario with seed data most relevant to this feature');
|
|
948
|
+
console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
|
|
949
|
+
console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
|
|
950
|
+
checkbox('Load that scenario to seed the database and preview it:');
|
|
951
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
952
|
+
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
953
|
+
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
954
|
+
console.log();
|
|
955
|
+
}
|
|
751
956
|
console.log(chalk.bold('Checklist:'));
|
|
752
957
|
checkbox('Create API routes that read from the database via Prisma');
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
958
|
+
if (!projectExists) {
|
|
959
|
+
checkbox('Seed the database with demo data');
|
|
960
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
961
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
962
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
963
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
964
|
+
}
|
|
758
965
|
checkbox('Verify the dev server shows the changes');
|
|
759
966
|
checkbox('If the feature involves auth, email, payments, or other common patterns: read FEATURE_PATTERNS.md');
|
|
760
967
|
// Responsive design guidance when building a mobile-responsive web app
|
|
@@ -896,34 +1103,7 @@ function printStep4(root, feature) {
|
|
|
896
1103
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
897
1104
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 5.'));
|
|
898
1105
|
console.log();
|
|
899
|
-
|
|
900
|
-
console.log(chalk.yellow(' After extraction, a page/route file should import and compose components — nothing else.'));
|
|
901
|
-
console.log(chalk.yellow(' Every distinct visual section in the page is its own component.'));
|
|
902
|
-
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
903
|
-
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
904
|
-
console.log();
|
|
905
|
-
console.log(chalk.bold('Checklist:'));
|
|
906
|
-
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
907
|
-
checkbox('Read EVERY file created or modified in this session');
|
|
908
|
-
console.log(chalk.yellow(' For EACH file, identify EVERY extractable piece:'));
|
|
909
|
-
console.log(chalk.yellow(' Components: headers, nav, loading states, empty states, error states,'));
|
|
910
|
-
console.log(chalk.yellow(' badges/pills, image containers, card sections, form fields, modals,'));
|
|
911
|
-
console.log(chalk.yellow(' titles, descriptions, rating displays, status indicators, buttons'));
|
|
912
|
-
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
913
|
-
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
914
|
-
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
915
|
-
console.log();
|
|
916
|
-
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
917
|
-
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
918
|
-
console.log(chalk.yellow(' No raw <div>, <span>, <h1>, <p>, <img>, or <ul> in page files.'));
|
|
919
|
-
console.log(chalk.yellow(' Every visual section in every component is itself a component.'));
|
|
920
|
-
console.log();
|
|
921
|
-
console.log(chalk.yellow(' For each item in the plan, note:'));
|
|
922
|
-
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
923
|
-
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
924
|
-
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
925
|
-
console.log();
|
|
926
|
-
console.log(chalk.dim('Present the numbered plan, then proceed to step 5 to execute it.'));
|
|
1106
|
+
printExtractionPlanInstructions();
|
|
927
1107
|
stopGate(4);
|
|
928
1108
|
}
|
|
929
1109
|
// ─── Step 5: Extract ──────────────────────────────────────────────────
|
|
@@ -998,20 +1178,7 @@ function printStep6(root, feature) {
|
|
|
998
1178
|
}
|
|
999
1179
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
1000
1180
|
console.log();
|
|
1001
|
-
|
|
1002
|
-
checkbox("Read `.codeyam/glossary.json` (create if it doesn't exist)");
|
|
1003
|
-
checkbox('Add an entry for each new function/component extracted in step 5');
|
|
1004
|
-
checkbox('Each entry should have: name, filePath, description, parameters, returnType, tags, feature');
|
|
1005
|
-
checkbox('Every non-component entry MUST have `testFile` set — hooks and functions all need tests');
|
|
1006
|
-
console.log(chalk.yellow(' If a function/hook has no test yet, go back and write one (TDD). The audit will block you.'));
|
|
1007
|
-
console.log();
|
|
1008
|
-
console.log(chalk.bold('Entry format:'));
|
|
1009
|
-
console.log(chalk.dim(' { "name": "calculateTotal", "filePath": "app/utils/pricing.ts",'));
|
|
1010
|
-
console.log(chalk.dim(' "description": "Calculates total price including tax and discounts",'));
|
|
1011
|
-
console.log(chalk.dim(' "parameters": [{ "name": "items", "type": "CartItem[]" }],'));
|
|
1012
|
-
console.log(chalk.dim(' "returnType": "number", "tags": ["pricing"],'));
|
|
1013
|
-
console.log(chalk.dim(' "testFile": "app/utils/pricing.test.ts",'));
|
|
1014
|
-
console.log(chalk.dim(` "feature": "${feature}", "createdAt": "..." }`));
|
|
1181
|
+
printGlossaryInstructions(feature);
|
|
1015
1182
|
console.log();
|
|
1016
1183
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
1017
1184
|
stopGate(6);
|
|
@@ -1039,56 +1206,11 @@ function printStep7(root, feature) {
|
|
|
1039
1206
|
console.log();
|
|
1040
1207
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
1041
1208
|
checkbox('List all files with new/modified visual components from step 5');
|
|
1042
|
-
checkbox('Create isolation route dirs: `codeyam editor isolate "ComponentA ComponentB ..."`');
|
|
1043
|
-
console.log(chalk.dim(' This creates app/isolated-components/layout.tsx (with production notFound() guard) and'));
|
|
1044
|
-
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
1045
1209
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
1046
1210
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1047
1211
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1048
1212
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1049
|
-
|
|
1050
|
-
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
1051
|
-
console.log(chalk.dim(' — Props/interface'));
|
|
1052
|
-
console.log(chalk.dim(' — Container width in the real app (e.g. card in a 3-col grid → max-w-sm)'));
|
|
1053
|
-
console.log(chalk.dim(' 2. Plan multiple scenarios that exercise the component like tests:'));
|
|
1054
|
-
console.log(chalk.dim(' — Default/happy path with typical data'));
|
|
1055
|
-
console.log(chalk.dim(' — Edge cases: empty/missing data, long text, maximum items, zero counts'));
|
|
1056
|
-
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
1057
|
-
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
1058
|
-
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
1059
|
-
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
1060
|
-
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
1061
|
-
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
1062
|
-
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
1063
|
-
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
1064
|
-
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block", padding:"20px" }}>'));
|
|
1065
|
-
console.log(chalk.dim(' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
1066
|
-
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth:"24rem", full-width component → omit maxWidth'));
|
|
1067
|
-
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
1068
|
-
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
1069
|
-
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
1070
|
-
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
1071
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
1072
|
-
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
1073
|
-
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
1074
|
-
console.log(chalk.dim(` "dimensions":["${dim}"],`));
|
|
1075
|
-
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1076
|
-
console.log(chalk.yellow(' ALWAYS include "dimensions" — use the named screen size that matches the component\'s context.'));
|
|
1077
|
-
console.log(chalk.dim(dimNames.length > 0
|
|
1078
|
-
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1079
|
-
: ` Use "${dim}" for most components. Names must match a key in screenSizes from setup.`));
|
|
1080
|
-
console.log(chalk.dim(' Names must match a key in screenSizes from setup. If you need a new size, add it first:'));
|
|
1081
|
-
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
1082
|
-
console.log(chalk.dim(` -d '{"screenSizes":{...existing..., "New Name":{"width":W,"height":H}}}' (replaces all — include every size)`));
|
|
1083
|
-
console.log(chalk.dim(' url is a PATH (starts with /) — the proxy routes it and intercepts API calls'));
|
|
1084
|
-
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
1085
|
-
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
1086
|
-
console.log(chalk.dim(' For large JSON: use your Write tool to create .codeyam/tmp/scenario.json,'));
|
|
1087
|
-
console.log(chalk.dim(' then: codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
1088
|
-
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
1089
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
1090
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1091
|
-
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
1213
|
+
printComponentCaptureInstructions();
|
|
1092
1214
|
console.log();
|
|
1093
1215
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1094
1216
|
checkbox('Run ALL test files created in step 5');
|
|
@@ -1143,51 +1265,11 @@ function printStep8(root, feature) {
|
|
|
1143
1265
|
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1144
1266
|
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
1145
1267
|
console.log();
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
console.log(chalk.
|
|
1150
|
-
|
|
1151
|
-
console.log(chalk.dim(' Use the SAME name to update. Enhance seed data to showcase this feature where relevant.'));
|
|
1152
|
-
console.log(chalk.yellow(" Don't just re-register unchanged data — enrich it so the feature is thoroughly demonstrated."));
|
|
1153
|
-
checkbox('ALWAYS include "dimensions" — pick the viewport that best demonstrates the page');
|
|
1154
|
-
console.log(chalk.yellow(' Every app scenario MUST have a "dimensions" field referencing a named screen size from setup.'));
|
|
1155
|
-
console.log(chalk.dim(dimNames.length > 0
|
|
1156
|
-
? ` Available dimensions: ${dimNames.map((n) => `"${n}"`).join(', ')}. Default: "${dim}".`
|
|
1157
|
-
: ` Use "${dim}" for most scenarios. Names must match a key in screenSizes from setup.`));
|
|
1158
|
-
if (dimNames.length > 1) {
|
|
1159
|
-
const otherDim = dimNames.find((n) => n !== dim) || dimNames[1];
|
|
1160
|
-
console.log(chalk.yellow(` Think about what fits each scenario: full pages/standalone views → "${otherDim}", popups/widgets → "${dim}".`));
|
|
1161
|
-
}
|
|
1162
|
-
console.log(chalk.dim(' For responsive apps, include multiple dimensions: "dimensions":["Desktop","Mobile"] captures both viewports.'));
|
|
1163
|
-
console.log(chalk.dim(' If you need a new named size mid-workflow, add it via editor-project-info API (include ALL existing sizes — the API replaces, not merges).'));
|
|
1164
|
-
checkbox('If the project has a database + seed adapter, use seed-based scenarios:');
|
|
1165
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Full Catalog","type":"application","url":"/","dimensions":["${dim}"],"seed":{"products":[...],"categories":[...]}}'`));
|
|
1166
|
-
console.log(chalk.dim(' Seed data is written to the DB via the seed adapter. Real app renders real data.'));
|
|
1167
|
-
checkbox('IMPORTANT: Always include "url" — the page path to screenshot for this scenario');
|
|
1168
|
-
console.log(chalk.dim(' Use "/" for home page, "/drinks/1" for detail pages, etc.'));
|
|
1169
|
-
console.log(chalk.dim(' Without url, the screenshot captures the root page regardless of the scenario.'));
|
|
1170
|
-
console.log(chalk.yellow(' Create separate scenarios for each page that shows different data.'));
|
|
1171
|
-
checkbox('For large seed data, use your Write tool to create .codeyam/tmp/scenario.json, then:');
|
|
1172
|
-
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenario.json'));
|
|
1173
|
-
console.log(chalk.yellow(' IMPORTANT: Use the Write tool — NOT cat/heredoc — to avoid permission prompts'));
|
|
1174
|
-
checkbox('For external API mocks (Stripe, weather, etc.), add externalApis:');
|
|
1175
|
-
console.log(chalk.dim(` "externalApis":{"GET https://api.stripe.com/v1/prices":{"body":[...],"status":200}}`));
|
|
1176
|
-
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
1177
|
-
checkbox('If the app uses localStorage/sessionStorage instead of a database, use localStorage scenarios:');
|
|
1178
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Full Library","type":"application","url":"/","dimensions":["${dim}"],"localStorage":{"articles":[...],"collections":[...]}}'`));
|
|
1179
|
-
console.log(chalk.dim(' The proxy injects data into localStorage before the app loads. Fully interactive — the app can read and write normally.'));
|
|
1180
|
-
console.log(chalk.dim(' For an empty state, register with an empty localStorage or omit it entirely.'));
|
|
1181
|
-
checkbox('If no database and no localStorage, use component-style mock scenarios:');
|
|
1182
|
-
console.log(chalk.dim(` codeyam editor register '{"name":"Empty","description":"...","url":"/","dimensions":["${dim}"],"mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
1183
|
-
checkbox('After each registration, check the response for `clientErrors`');
|
|
1184
|
-
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1185
|
-
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1186
|
-
checkbox('CRITICAL: After EACH registration, view the captured screenshot to verify it');
|
|
1187
|
-
console.log(chalk.yellow(' Read the screenshot file path from the registration response and view it.'));
|
|
1188
|
-
console.log(chalk.yellow(' Does the screenshot actually SHOW the data you put in? If you seeded 5 articles'));
|
|
1189
|
-
console.log(chalk.yellow(' but the screenshot shows an empty page, the scenario is broken — fix the code or data.'));
|
|
1190
|
-
console.log(chalk.yellow(' Do NOT create scenarios with data the app cannot yet render.'));
|
|
1268
|
+
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1269
|
+
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1270
|
+
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1271
|
+
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1272
|
+
printAppScenarioInstructions();
|
|
1191
1273
|
console.log();
|
|
1192
1274
|
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 10 if needed.'));
|
|
1193
1275
|
console.log();
|
|
@@ -1379,41 +1461,654 @@ function printStep13(root, feature) {
|
|
|
1379
1461
|
if (isResuming) {
|
|
1380
1462
|
printResumptionHeader(13);
|
|
1381
1463
|
}
|
|
1382
|
-
console.log('Present the results to the user and get their approval.');
|
|
1464
|
+
console.log('Present the results to the user and get their approval.');
|
|
1465
|
+
console.log();
|
|
1466
|
+
console.log(chalk.bold('Checklist:'));
|
|
1467
|
+
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1468
|
+
console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
|
|
1469
|
+
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1470
|
+
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1471
|
+
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1472
|
+
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1473
|
+
checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
|
|
1474
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1475
|
+
console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
|
|
1476
|
+
printDimensionGuidance(dim, dimNames);
|
|
1477
|
+
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1478
|
+
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1479
|
+
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1480
|
+
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1481
|
+
checkbox('Report test count and audit status (one line)');
|
|
1482
|
+
console.log();
|
|
1483
|
+
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1484
|
+
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1485
|
+
chalk.dim(' — git commit all changes and record in journal'));
|
|
1486
|
+
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1487
|
+
chalk.dim(' — describe changes, then re-verify'));
|
|
1488
|
+
console.log();
|
|
1489
|
+
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1490
|
+
checkbox('Advance to the commit step: `codeyam editor 14`');
|
|
1491
|
+
console.log();
|
|
1492
|
+
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1493
|
+
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
1494
|
+
checkbox('Ask what changes the user wants (if not already clear)');
|
|
1495
|
+
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1496
|
+
checkbox('THEN make the requested changes and follow the checklist');
|
|
1497
|
+
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1498
|
+
stopGate(13, { confirm: true });
|
|
1499
|
+
}
|
|
1500
|
+
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1501
|
+
/**
|
|
1502
|
+
* Print a progress tracker for the 8 migration steps.
|
|
1503
|
+
*/
|
|
1504
|
+
function printMigrationProgressTracker(current, pageName, pageIndex, totalPages) {
|
|
1505
|
+
console.log();
|
|
1506
|
+
console.log(chalk.dim(` Migration: Page ${pageIndex + 1}/${totalPages} (${pageName})`));
|
|
1507
|
+
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
1508
|
+
for (let i = 1; i <= 10; i++) {
|
|
1509
|
+
const label = MIGRATION_STEP_LABELS[i];
|
|
1510
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
1511
|
+
const content = `${num}. ${label.padEnd(28)}`;
|
|
1512
|
+
if (i < current) {
|
|
1513
|
+
console.log(chalk.dim(' │') + chalk.green(` ✓ ${content}`) + chalk.dim('│'));
|
|
1514
|
+
}
|
|
1515
|
+
else if (i === current) {
|
|
1516
|
+
console.log(chalk.dim(' │') + chalk.bold.cyan(` → ${content}`) + chalk.dim('│'));
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
console.log(chalk.dim(` │ ○ ${content}│`));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
console.log(chalk.dim(' └─────────────────────────────────────┘'));
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Print a STOP gate for migration steps.
|
|
1526
|
+
*/
|
|
1527
|
+
function migrationStopGate(current, pageName, pageIndex, totalPages, opts) {
|
|
1528
|
+
console.log();
|
|
1529
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
1530
|
+
console.log();
|
|
1531
|
+
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
1532
|
+
if (opts?.confirm) {
|
|
1533
|
+
console.log();
|
|
1534
|
+
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
1535
|
+
}
|
|
1536
|
+
console.log();
|
|
1537
|
+
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
1538
|
+
printMigrationProgressTracker(current, pageName, pageIndex, totalPages);
|
|
1539
|
+
console.log();
|
|
1540
|
+
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
1541
|
+
console.log();
|
|
1542
|
+
if (current === 5) {
|
|
1543
|
+
// Step 5 (Discuss) can skip to step 9 if user declines decomposition
|
|
1544
|
+
console.log(chalk.green('If decomposing, run: ') +
|
|
1545
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1546
|
+
console.log(chalk.green('If skipping decomposition, run: ') +
|
|
1547
|
+
chalk.bold('codeyam editor migrate 9'));
|
|
1548
|
+
}
|
|
1549
|
+
else if (current < 10) {
|
|
1550
|
+
console.log(chalk.green('When done, run: ') +
|
|
1551
|
+
chalk.bold(`codeyam editor migrate ${current + 1}`));
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
console.log(chalk.green('Page complete! Run: ') +
|
|
1555
|
+
chalk.bold('codeyam editor migrate next') +
|
|
1556
|
+
chalk.green(' to start the next page'));
|
|
1557
|
+
}
|
|
1558
|
+
console.log();
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Write migration-aware editor state.
|
|
1562
|
+
*/
|
|
1563
|
+
function writeMigrationStepState(root, step, pageName, pageIndex, totalPages) {
|
|
1564
|
+
const prevState = readState(root);
|
|
1565
|
+
const isResuming = prevState?.step === step && !!prevState?.migration;
|
|
1566
|
+
const now = new Date().toISOString();
|
|
1567
|
+
writeState(root, {
|
|
1568
|
+
feature: `Migration: ${pageName}`,
|
|
1569
|
+
step,
|
|
1570
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1571
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1572
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1573
|
+
migration: {
|
|
1574
|
+
pageName,
|
|
1575
|
+
pageIndex,
|
|
1576
|
+
totalPages,
|
|
1577
|
+
},
|
|
1578
|
+
});
|
|
1579
|
+
logEvent(root, 'step', {
|
|
1580
|
+
step,
|
|
1581
|
+
label: MIGRATION_STEP_LABELS[step],
|
|
1582
|
+
feature: `Migration: ${pageName}`,
|
|
1583
|
+
migration: true,
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Get the current migration page info from state, or return null.
|
|
1588
|
+
*/
|
|
1589
|
+
function getCurrentMigrationPage(root) {
|
|
1590
|
+
const migState = readMigrationState(root);
|
|
1591
|
+
if (!migState || migState.pages.length === 0)
|
|
1592
|
+
return null;
|
|
1593
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
1594
|
+
if (!page)
|
|
1595
|
+
return null;
|
|
1596
|
+
return {
|
|
1597
|
+
pageName: page.name,
|
|
1598
|
+
pageIndex: migState.currentPageIndex,
|
|
1599
|
+
totalPages: migState.pages.length,
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
// ─── Migration Step 1: Survey ───────────────────────────────────────
|
|
1603
|
+
function printMigrateStep1(root) {
|
|
1604
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1605
|
+
if (!pageInfo) {
|
|
1606
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1607
|
+
process.exit(1);
|
|
1608
|
+
}
|
|
1609
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1610
|
+
writeMigrationStepState(root, 1, pageName, pageIndex, totalPages);
|
|
1611
|
+
const migState = readMigrationState(root);
|
|
1612
|
+
const page = migState.pages[pageIndex];
|
|
1613
|
+
console.log();
|
|
1614
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 1: Survey — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1615
|
+
console.log();
|
|
1616
|
+
console.log(chalk.bold('Goal: Survey the page, understand its structure, and start the dev server.'));
|
|
1617
|
+
console.log();
|
|
1618
|
+
console.log(chalk.bold('Survey:'));
|
|
1619
|
+
checkbox(`Read the page file: \`${page.filePath}\``);
|
|
1620
|
+
checkbox('Follow every import — read all local components, hooks, utilities used by this page');
|
|
1621
|
+
checkbox('Map the data flow: where does data come from? Server actions? API routes? Props?');
|
|
1622
|
+
checkbox('Check `.codeyam/glossary.json` for components already extracted from previous pages');
|
|
1623
|
+
checkbox('Check `.codeyam/migration-state.json` sharedComponents for reusable pieces');
|
|
1624
|
+
checkbox('Note the page route and any dynamic segments');
|
|
1625
|
+
console.log();
|
|
1626
|
+
console.log(chalk.bold('Dev Server:'));
|
|
1627
|
+
checkbox('If the dev server is not running yet, figure out how to start it (check package.json scripts)');
|
|
1628
|
+
checkbox('FIRST: Verify dependencies are installed (check if node_modules/ exists and is non-empty)');
|
|
1629
|
+
console.log(chalk.yellow(' If node_modules is missing or sparse, run the package manager install command first'));
|
|
1630
|
+
console.log(chalk.dim(' Missing deps cause cryptic 500 errors at render time — install BEFORE starting the server'));
|
|
1631
|
+
checkbox('Start the dev server: `codeyam editor dev-server \'{"action":"start"}\'`');
|
|
1632
|
+
checkbox("Verify it's running: check for successful startup output");
|
|
1633
|
+
console.log();
|
|
1634
|
+
migrationStopGate(1, pageName, pageIndex, totalPages);
|
|
1635
|
+
}
|
|
1636
|
+
// ─── Migration Step 2: App Scenarios ────────────────────────────────
|
|
1637
|
+
function printMigrateStep2(root) {
|
|
1638
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1639
|
+
if (!pageInfo) {
|
|
1640
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1643
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1644
|
+
writeMigrationStepState(root, 2, pageName, pageIndex, totalPages);
|
|
1645
|
+
const migState = readMigrationState(root);
|
|
1646
|
+
const page = migState.pages[pageIndex];
|
|
1647
|
+
console.log();
|
|
1648
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 2: App Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1649
|
+
console.log();
|
|
1650
|
+
console.log(chalk.bold(`Goal: Register application scenarios that screenshot the ACTUAL page at "${page.route}".`));
|
|
1651
|
+
console.log();
|
|
1652
|
+
console.log(chalk.yellow(' App scenarios capture how the full page looks with different data states.'));
|
|
1653
|
+
console.log(chalk.yellow(` Every scenario MUST use the real page route ("${page.route}") — NOT isolation routes.`));
|
|
1654
|
+
console.log(chalk.yellow(' These are the primary scenarios the user will see for this page.'));
|
|
1655
|
+
console.log();
|
|
1656
|
+
console.log(chalk.bold('Seed Adapter Setup:'));
|
|
1657
|
+
checkbox('Check if a seed adapter exists: `ls .codeyam/seed-adapter.ts 2>/dev/null`');
|
|
1658
|
+
console.log(chalk.yellow(' If NO seed adapter exists, create one. The seed adapter wipes and re-seeds the database'));
|
|
1659
|
+
console.log(chalk.yellow(' for each scenario, enabling different data states on the real page.'));
|
|
1660
|
+
console.log();
|
|
1661
|
+
console.log(chalk.dim(' How to create a seed adapter:'));
|
|
1662
|
+
console.log(chalk.dim(' 1. Identify the database technology from your step 1 survey (Prisma, Supabase, Drizzle, etc.)'));
|
|
1663
|
+
console.log(chalk.dim(' 2. Check for a matching template: `ls .codeyam/seed-adapters/`'));
|
|
1664
|
+
console.log(chalk.dim(' 3. For Supabase: `cp .codeyam/seed-adapters/supabase.ts .codeyam/seed-adapter.ts`'));
|
|
1665
|
+
console.log(chalk.dim(' The seed adapter auto-detects Supabase credentials by scanning env var VALUES (not names).'));
|
|
1666
|
+
console.log(chalk.dim(' It reads .env / .env.local and finds: *.supabase.co URLs, sb_secret_* keys, and legacy JWTs.'));
|
|
1667
|
+
console.log(chalk.dim(' Do NOT grep for specific env var names — just register a scenario and try it.'));
|
|
1668
|
+
console.log(chalk.dim(' If it fails, the error message will say exactly what credentials are missing.'));
|
|
1669
|
+
console.log(chalk.dim(' 4. For Prisma: write a seed adapter that uses your Prisma client to delete/insert rows'));
|
|
1670
|
+
console.log(chalk.dim(' 5. For other databases: write a .codeyam/seed-adapter.ts that reads a JSON file (argv[2]),'));
|
|
1671
|
+
console.log(chalk.dim(' deletes all rows from each table, then inserts the seed rows. Format: {"table": [{...}]}'));
|
|
1672
|
+
console.log(chalk.dim(' 6. Test it: write a small test seed JSON and run `npx tsx .codeyam/seed-adapter.ts test.json`'));
|
|
1673
|
+
console.log();
|
|
1674
|
+
printAppScenarioInstructions(pageName, page.route);
|
|
1675
|
+
console.log();
|
|
1676
|
+
migrationStopGate(2, pageName, pageIndex, totalPages);
|
|
1677
|
+
}
|
|
1678
|
+
// ─── Migration Step 3: Component Scenarios ──────────────────────────
|
|
1679
|
+
function printMigrateStep3(root) {
|
|
1680
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1681
|
+
if (!pageInfo) {
|
|
1682
|
+
console.error(chalk.red('Error: No migration in progress. Run `codeyam editor migrate` first.'));
|
|
1683
|
+
process.exit(1);
|
|
1684
|
+
}
|
|
1685
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1686
|
+
writeMigrationStepState(root, 3, pageName, pageIndex, totalPages);
|
|
1687
|
+
console.log();
|
|
1688
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 3: Component Scenarios — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1689
|
+
console.log();
|
|
1690
|
+
console.log(chalk.bold('Goal: Capture individual component scenarios for major visual components on this page.'));
|
|
1691
|
+
console.log();
|
|
1692
|
+
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1693
|
+
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1694
|
+
console.log();
|
|
1695
|
+
printComponentCaptureInstructions();
|
|
1696
|
+
console.log();
|
|
1697
|
+
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1698
|
+
}
|
|
1699
|
+
// ─── Migration Step 4: Preview ──────────────────────────────────────
|
|
1700
|
+
function printMigrateStep4(root) {
|
|
1701
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1702
|
+
if (!pageInfo) {
|
|
1703
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1707
|
+
writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
|
|
1708
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1709
|
+
console.log();
|
|
1710
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1711
|
+
console.log();
|
|
1712
|
+
console.log(chalk.bold('Goal: Verify screenshots look correct and show results to the user.'));
|
|
1713
|
+
console.log();
|
|
1714
|
+
console.log(chalk.bold('Checklist:'));
|
|
1715
|
+
checkbox('Review all scenario screenshots for this page — do they look correct?');
|
|
1716
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1717
|
+
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1718
|
+
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1719
|
+
checkbox('Pick the best scenario to showcase:');
|
|
1720
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1721
|
+
printDimensionGuidance(dim, dimNames);
|
|
1722
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1723
|
+
console.log();
|
|
1724
|
+
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1725
|
+
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
1726
|
+
}
|
|
1727
|
+
// ─── Migration Step 5: Discuss ──────────────────────────────────────
|
|
1728
|
+
function printMigrateStep5(root) {
|
|
1729
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1730
|
+
if (!pageInfo) {
|
|
1731
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1732
|
+
process.exit(1);
|
|
1733
|
+
}
|
|
1734
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1735
|
+
writeMigrationStepState(root, 5, pageName, pageIndex, totalPages);
|
|
1736
|
+
console.log();
|
|
1737
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 5: Discuss — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1738
|
+
console.log();
|
|
1739
|
+
console.log(chalk.bold('Goal: Assess page complexity and ask the user if decomposition is worth it.'));
|
|
1740
|
+
console.log();
|
|
1741
|
+
console.log(chalk.bold('Checklist:'));
|
|
1742
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1743
|
+
checkbox("Assess the page's complexity:");
|
|
1744
|
+
console.log(chalk.dim(' — How many inline components exist?'));
|
|
1745
|
+
console.log(chalk.dim(' — How much business logic is embedded in the page file?'));
|
|
1746
|
+
console.log(chalk.dim(' — Are there reusable pieces that other pages could share?'));
|
|
1747
|
+
console.log(chalk.dim(' — Would extraction improve testability or maintainability?'));
|
|
1748
|
+
checkbox('Present your assessment to the user');
|
|
1749
|
+
console.log();
|
|
1750
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1751
|
+
console.log(chalk.green(' Option 1 label: "Yes, decompose this page"') +
|
|
1752
|
+
chalk.dim(' — continue to step 6 (Decompose)'));
|
|
1753
|
+
console.log(chalk.yellow(' Option 2 label: "No, skip decomposition"') +
|
|
1754
|
+
chalk.dim(' — skip to step 9 (Journal)'));
|
|
1755
|
+
console.log();
|
|
1756
|
+
console.log(chalk.bold.yellow('Routing:'));
|
|
1757
|
+
console.log(chalk.yellow(' If user chooses decomposition: ') +
|
|
1758
|
+
chalk.bold('codeyam editor migrate 6'));
|
|
1759
|
+
console.log(chalk.yellow(' If user skips: ') + chalk.bold('codeyam editor migrate 9'));
|
|
1760
|
+
migrationStopGate(5, pageName, pageIndex, totalPages, { confirm: true });
|
|
1761
|
+
}
|
|
1762
|
+
// ─── Migration Step 6: Decompose ────────────────────────────────────
|
|
1763
|
+
function printMigrateStep6(root) {
|
|
1764
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1765
|
+
if (!pageInfo) {
|
|
1766
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1767
|
+
process.exit(1);
|
|
1768
|
+
}
|
|
1769
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1770
|
+
writeMigrationStepState(root, 6, pageName, pageIndex, totalPages);
|
|
1771
|
+
console.log();
|
|
1772
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 6: Decompose — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1773
|
+
console.log();
|
|
1774
|
+
console.log(chalk.bold('Goal: Plan all extractions. Mark already-extracted shared components as REUSE.'));
|
|
1775
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1776
|
+
console.log();
|
|
1777
|
+
console.log(chalk.bold.yellow('Migration note: check glossary for reuse opportunities'));
|
|
1778
|
+
console.log(chalk.yellow(' If a component exists in the glossary from a previous page, mark it as REUSE in your plan.'));
|
|
1779
|
+
console.log(chalk.yellow(' Only add it to the extraction plan if it needs modifications for this page.'));
|
|
1780
|
+
console.log();
|
|
1781
|
+
printExtractionPlanInstructions();
|
|
1782
|
+
migrationStopGate(6, pageName, pageIndex, totalPages);
|
|
1783
|
+
}
|
|
1784
|
+
// ─── Migration Step 7: Extract ──────────────────────────────────────
|
|
1785
|
+
function printMigrateStep7(root) {
|
|
1786
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1787
|
+
if (!pageInfo) {
|
|
1788
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1789
|
+
process.exit(1);
|
|
1790
|
+
}
|
|
1791
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1792
|
+
writeMigrationStepState(root, 7, pageName, pageIndex, totalPages);
|
|
1793
|
+
console.log();
|
|
1794
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 7: Extract — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1795
|
+
console.log();
|
|
1796
|
+
console.log(chalk.bold('Goal: Execute the extraction plan. Components first, then functions via TDD.'));
|
|
1797
|
+
console.log();
|
|
1798
|
+
console.log(chalk.bold('Checklist:'));
|
|
1799
|
+
checkbox('Extract visual components (no tests needed for components)');
|
|
1800
|
+
checkbox('Extract library functions via TDD (write failing test first, then implement)');
|
|
1801
|
+
checkbox('Extract hooks as needed');
|
|
1802
|
+
checkbox('Recursive pass: check each extracted component for further extraction');
|
|
1803
|
+
console.log(chalk.yellow(' Each component should be a thin shell composing sub-components.'));
|
|
1804
|
+
checkbox('Verify the page file is ONLY imports + component composition');
|
|
1805
|
+
checkbox('Run tests to confirm nothing is broken: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1806
|
+
console.log();
|
|
1807
|
+
console.log(chalk.dim('After extraction, the page should be a thin shell. Move to step 8 to recapture.'));
|
|
1808
|
+
migrationStopGate(7, pageName, pageIndex, totalPages);
|
|
1809
|
+
}
|
|
1810
|
+
// ─── Migration Step 8: Recapture ────────────────────────────────────
|
|
1811
|
+
function printMigrateStep8(root) {
|
|
1812
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1813
|
+
if (!pageInfo) {
|
|
1814
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
}
|
|
1817
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1818
|
+
writeMigrationStepState(root, 8, pageName, pageIndex, totalPages);
|
|
1819
|
+
const migState = readMigrationState(root);
|
|
1820
|
+
const page = migState.pages[pageIndex];
|
|
1821
|
+
console.log();
|
|
1822
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 8: Recapture — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1823
|
+
console.log();
|
|
1824
|
+
console.log(chalk.bold('Goal: Update glossary, re-register ALL scenarios after code changes, and verify.'));
|
|
1825
|
+
console.log();
|
|
1826
|
+
console.log(chalk.bold('Glossary:'));
|
|
1827
|
+
printGlossaryInstructions();
|
|
1828
|
+
console.log(chalk.yellow(' Skip entries already in the glossary from previous page migrations.'));
|
|
1829
|
+
checkbox('Update sharedComponents in `.codeyam/migration-state.json` if any extracted components are shared');
|
|
1830
|
+
console.log();
|
|
1831
|
+
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1832
|
+
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1833
|
+
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1834
|
+
checkbox('Find all scenarios that use the real page route (not /codeyam-isolate/ paths)');
|
|
1835
|
+
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1836
|
+
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1837
|
+
checkbox('After each registration, check the response for `clientErrors`');
|
|
1838
|
+
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
1839
|
+
console.log();
|
|
1840
|
+
console.log(chalk.bold('Component Scenarios:'));
|
|
1841
|
+
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1842
|
+
printComponentCaptureInstructions();
|
|
1843
|
+
console.log();
|
|
1844
|
+
console.log(chalk.bold('Verify:'));
|
|
1845
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
1846
|
+
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1847
|
+
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1848
|
+
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1849
|
+
checkbox('Fix any issues before proceeding');
|
|
1850
|
+
console.log();
|
|
1851
|
+
migrationStopGate(8, pageName, pageIndex, totalPages);
|
|
1852
|
+
}
|
|
1853
|
+
// ─── Migration Step 9: Journal ──────────────────────────────────────
|
|
1854
|
+
function printMigrateStep9(root) {
|
|
1855
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1856
|
+
if (!pageInfo) {
|
|
1857
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1858
|
+
process.exit(1);
|
|
1859
|
+
}
|
|
1860
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1861
|
+
writeMigrationStepState(root, 9, pageName, pageIndex, totalPages);
|
|
1862
|
+
console.log();
|
|
1863
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 9: Journal — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1864
|
+
console.log();
|
|
1865
|
+
console.log(`Create a journal entry documenting the migration of the ${pageName} page.`);
|
|
1866
|
+
console.log();
|
|
1867
|
+
console.log(chalk.bold('Checklist:'));
|
|
1868
|
+
checkbox('Write a concise description of what was migrated (2-3 sentences)');
|
|
1869
|
+
checkbox(`Create journal: \`codeyam editor journal '{"title":"Migration: ${pageName}","type":"migration","description":"...","includeSessionScenarios":true}'\``);
|
|
1870
|
+
console.log();
|
|
1871
|
+
migrationStopGate(9, pageName, pageIndex, totalPages);
|
|
1872
|
+
}
|
|
1873
|
+
// ─── Migration Step 10: Present ─────────────────────────────────────
|
|
1874
|
+
function printMigrateStep10(root) {
|
|
1875
|
+
const pageInfo = getCurrentMigrationPage(root);
|
|
1876
|
+
if (!pageInfo) {
|
|
1877
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
1878
|
+
process.exit(1);
|
|
1879
|
+
}
|
|
1880
|
+
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1881
|
+
writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
|
|
1882
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1883
|
+
console.log();
|
|
1884
|
+
console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1885
|
+
console.log();
|
|
1886
|
+
console.log("Show results and commit this page's migration.");
|
|
1383
1887
|
console.log();
|
|
1384
1888
|
console.log(chalk.bold('Checklist:'));
|
|
1385
1889
|
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1386
|
-
|
|
1387
|
-
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1388
|
-
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1389
|
-
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1390
|
-
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1391
|
-
checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
|
|
1890
|
+
checkbox('Pick the best scenario to showcase:');
|
|
1392
1891
|
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1393
|
-
console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
|
|
1394
1892
|
printDimensionGuidance(dim, dimNames);
|
|
1395
|
-
checkbox(
|
|
1396
|
-
|
|
1397
|
-
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1398
|
-
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1399
|
-
checkbox('Report test count and audit status (one line)');
|
|
1893
|
+
checkbox('Show results: `codeyam editor show-results`');
|
|
1894
|
+
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1400
1895
|
console.log();
|
|
1401
|
-
console.log(chalk.bold('Present a selection menu
|
|
1896
|
+
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
1402
1897
|
console.log(chalk.green(' Option 1 label: "Save & commit"') +
|
|
1403
|
-
chalk.dim(
|
|
1898
|
+
chalk.dim(" — commit this page's migration"));
|
|
1404
1899
|
console.log(chalk.yellow(' Option 2 label: "I\'d like to make some changes"') +
|
|
1405
|
-
chalk.dim(' —
|
|
1900
|
+
chalk.dim(' — make changes, then re-verify'));
|
|
1406
1901
|
console.log();
|
|
1407
1902
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1408
|
-
checkbox('
|
|
1903
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1904
|
+
checkbox(`Commit: \`codeyam editor commit '{"message":"migration: ${pageName} page\\n\\n<description>"}'\``);
|
|
1905
|
+
checkbox('Mark page complete: `codeyam editor migrate complete`');
|
|
1906
|
+
const remaining = pageInfo.totalPages - pageIndex - 1;
|
|
1907
|
+
if (remaining > 0) {
|
|
1908
|
+
checkbox('Advance to next page: `codeyam editor migrate next`');
|
|
1909
|
+
console.log();
|
|
1910
|
+
console.log(chalk.bold.green(`Page ${pageIndex + 1}/${totalPages} complete! ${remaining} page(s) remaining.`));
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
console.log();
|
|
1914
|
+
console.log(chalk.bold.green('Migration complete! All pages processed.'));
|
|
1915
|
+
console.log(chalk.green('The project is now fully migrated. Future sessions use the normal feature workflow.'));
|
|
1916
|
+
console.log(chalk.green('Start building: ') + chalk.bold('codeyam editor steps'));
|
|
1917
|
+
}
|
|
1409
1918
|
console.log();
|
|
1410
|
-
console.log(chalk.bold('If the user chooses "Make changes"
|
|
1411
|
-
checkbox(
|
|
1412
|
-
checkbox('Ask what changes the user wants
|
|
1413
|
-
checkbox(`Run: \`codeyam editor change "${
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1919
|
+
console.log(chalk.bold('If the user chooses "Make changes":'));
|
|
1920
|
+
checkbox('Hide results: `codeyam editor hide-results`');
|
|
1921
|
+
checkbox('Ask what changes the user wants');
|
|
1922
|
+
checkbox(`Run: \`codeyam editor change "Migration: ${pageName}"\` — follow the change checklist`);
|
|
1923
|
+
console.log();
|
|
1924
|
+
migrationStopGate(10, pageName, pageIndex, totalPages, { confirm: true });
|
|
1925
|
+
}
|
|
1926
|
+
// ─── Migration Survey ───────────────────────────────────────────────
|
|
1927
|
+
function printMigrateSurvey(root) {
|
|
1928
|
+
console.log();
|
|
1929
|
+
console.log(chalk.bold.cyan('━━━ Project Migration Survey ━━━'));
|
|
1930
|
+
console.log();
|
|
1931
|
+
console.log('Survey the existing project, present a migration plan, and confirm the order with the user.');
|
|
1932
|
+
console.log(chalk.dim('See the "Migration Survey" section in SKILL.md for the full checklist and migration-state.json format.'));
|
|
1933
|
+
console.log();
|
|
1934
|
+
// Write editor state so hooks and printCycleOverview detect migration
|
|
1935
|
+
const now = new Date().toISOString();
|
|
1936
|
+
writeState(root, {
|
|
1937
|
+
feature: 'Migration: Survey',
|
|
1938
|
+
step: 0,
|
|
1939
|
+
label: 'Survey',
|
|
1940
|
+
startedAt: now,
|
|
1941
|
+
featureStartedAt: now,
|
|
1942
|
+
migration: {
|
|
1943
|
+
pageName: 'Survey',
|
|
1944
|
+
pageIndex: -1,
|
|
1945
|
+
totalPages: 0,
|
|
1946
|
+
},
|
|
1947
|
+
});
|
|
1948
|
+
logEvent(root, 'migration-survey', {});
|
|
1949
|
+
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
1950
|
+
console.log();
|
|
1951
|
+
console.log(chalk.red('Survey the project and confirm the migration order with the user.'));
|
|
1952
|
+
console.log(chalk.green('After confirmation and writing migration-state.json: ') +
|
|
1953
|
+
chalk.bold('codeyam editor migrate 1'));
|
|
1954
|
+
console.log();
|
|
1955
|
+
}
|
|
1956
|
+
// ─── Migration Status ───────────────────────────────────────────────
|
|
1957
|
+
function printMigrationStatus(root) {
|
|
1958
|
+
const state = readMigrationState(root);
|
|
1959
|
+
if (!state) {
|
|
1960
|
+
console.log(chalk.dim('No migration in progress.'));
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
console.log();
|
|
1964
|
+
console.log(chalk.bold.cyan('━━━ Migration Progress ━━━'));
|
|
1965
|
+
console.log();
|
|
1966
|
+
const done = state.pages.filter((p) => p.status === 'complete').length;
|
|
1967
|
+
const total = state.pages.length;
|
|
1968
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
1969
|
+
console.log(` Status: ${chalk.bold(state.status)} — ${done}/${total} pages (${pct}%)`);
|
|
1970
|
+
console.log();
|
|
1971
|
+
for (let i = 0; i < state.pages.length; i++) {
|
|
1972
|
+
const p = state.pages[i];
|
|
1973
|
+
let icon;
|
|
1974
|
+
let color;
|
|
1975
|
+
if (p.status === 'complete') {
|
|
1976
|
+
icon = '✓';
|
|
1977
|
+
color = chalk.green;
|
|
1978
|
+
}
|
|
1979
|
+
else if (p.status === 'in-progress') {
|
|
1980
|
+
icon = '→';
|
|
1981
|
+
color = chalk.bold.cyan;
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
icon = '○';
|
|
1985
|
+
color = chalk.dim;
|
|
1986
|
+
}
|
|
1987
|
+
const extra = p.status === 'complete'
|
|
1988
|
+
? chalk.dim(` (${p.extractedComponents.length} components, ${p.extractedFunctions.length} functions, ${p.scenarioCount} scenarios)`)
|
|
1989
|
+
: '';
|
|
1990
|
+
console.log(` ${color(`${icon} ${i + 1}. ${p.name}`)} ${chalk.dim(`(${p.route})`)}${extra}`);
|
|
1991
|
+
}
|
|
1992
|
+
if (state.sharedComponents.length > 0) {
|
|
1993
|
+
console.log();
|
|
1994
|
+
console.log(chalk.bold('Shared Components:'));
|
|
1995
|
+
for (const sc of state.sharedComponents) {
|
|
1996
|
+
console.log(` ${chalk.cyan(sc.name)} ${chalk.dim(`— from ${sc.extractedFromPage}, used in: ${sc.usedInPages.join(', ')}`)}`);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
console.log();
|
|
2000
|
+
}
|
|
2001
|
+
// ─── Migration Command Dispatcher ───────────────────────────────────
|
|
2002
|
+
function handleMigrateCommand(root, subArg) {
|
|
2003
|
+
// No subcommand or explicit empty → run initial survey
|
|
2004
|
+
if (!subArg) {
|
|
2005
|
+
const migState = readMigrationState(root);
|
|
2006
|
+
if (migState?.status === 'in-progress') {
|
|
2007
|
+
// Already in progress — show status and resume hint
|
|
2008
|
+
printMigrationStatus(root);
|
|
2009
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2010
|
+
if (page) {
|
|
2011
|
+
const editorState = readState(root);
|
|
2012
|
+
const currentStep = editorState?.migration ? editorState.step : 1;
|
|
2013
|
+
console.log(chalk.green('Continue with: ') +
|
|
2014
|
+
chalk.bold(`codeyam editor migrate ${currentStep}`));
|
|
2015
|
+
console.log();
|
|
2016
|
+
}
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (migState?.status === 'complete') {
|
|
2020
|
+
console.log(chalk.green('Migration is complete! Use the normal feature workflow: `codeyam editor steps`'));
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
printMigrateSurvey(root);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
// Parse subcommand
|
|
2027
|
+
if (subArg === 'next') {
|
|
2028
|
+
const result = advanceToNextPage(root);
|
|
2029
|
+
if (!result) {
|
|
2030
|
+
const migState = readMigrationState(root);
|
|
2031
|
+
if (migState?.status === 'complete') {
|
|
2032
|
+
console.log();
|
|
2033
|
+
console.log(chalk.bold.green('🎉 Migration complete!'));
|
|
2034
|
+
console.log();
|
|
2035
|
+
const done = migState.pages.filter((p) => p.status === 'complete');
|
|
2036
|
+
const totalComponents = done.reduce((sum, p) => sum + p.extractedComponents.length, 0);
|
|
2037
|
+
const totalFunctions = done.reduce((sum, p) => sum + p.extractedFunctions.length, 0);
|
|
2038
|
+
const totalScenarios = done.reduce((sum, p) => sum + p.scenarioCount, 0);
|
|
2039
|
+
console.log(` Pages migrated: ${done.length}`);
|
|
2040
|
+
console.log(` Components extracted: ${totalComponents}`);
|
|
2041
|
+
console.log(` Functions extracted: ${totalFunctions}`);
|
|
2042
|
+
console.log(` Scenarios created: ${totalScenarios}`);
|
|
2043
|
+
console.log(` Shared components: ${migState.sharedComponents.length}`);
|
|
2044
|
+
console.log();
|
|
2045
|
+
console.log(chalk.green('The project is fully migrated. Start building features: `codeyam editor steps`'));
|
|
2046
|
+
console.log();
|
|
2047
|
+
}
|
|
2048
|
+
else {
|
|
2049
|
+
console.log(chalk.red('No pending pages found. Run `codeyam editor migrate status` to check progress.'));
|
|
2050
|
+
}
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
// Start M1 for the next page
|
|
2054
|
+
printMigrateStep1(root);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
if (subArg === 'status') {
|
|
2058
|
+
printMigrationStatus(root);
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
if (subArg === 'complete') {
|
|
2062
|
+
const migState = readMigrationState(root);
|
|
2063
|
+
if (!migState) {
|
|
2064
|
+
console.error(chalk.red('Error: No migration in progress.'));
|
|
2065
|
+
process.exit(1);
|
|
2066
|
+
}
|
|
2067
|
+
const page = migState.pages[migState.currentPageIndex];
|
|
2068
|
+
if (!page) {
|
|
2069
|
+
console.error(chalk.red('Error: No active migration page.'));
|
|
2070
|
+
process.exit(1);
|
|
2071
|
+
}
|
|
2072
|
+
completePage(root, {
|
|
2073
|
+
extractedComponents: page.extractedComponents,
|
|
2074
|
+
extractedFunctions: page.extractedFunctions,
|
|
2075
|
+
scenarioCount: page.scenarioCount,
|
|
2076
|
+
});
|
|
2077
|
+
console.log(chalk.green(`Page "${page.name}" marked complete.`));
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
// Numeric step: 1-8
|
|
2081
|
+
const step = parseInt(subArg, 10);
|
|
2082
|
+
if (isNaN(step) || step < 1 || step > 10) {
|
|
2083
|
+
console.error(chalk.red(`Error: Invalid migration step "${subArg}". Must be 1-10, "next", "complete", or "status".`));
|
|
2084
|
+
process.exit(1);
|
|
2085
|
+
}
|
|
2086
|
+
// Ensure migration is active and first page started
|
|
2087
|
+
const migState = readMigrationState(root);
|
|
2088
|
+
if (!migState) {
|
|
2089
|
+
console.error(chalk.red('Error: No migration state. Run `codeyam editor migrate` first.'));
|
|
2090
|
+
process.exit(1);
|
|
2091
|
+
}
|
|
2092
|
+
// If still in survey state and step 1 requested, start first page
|
|
2093
|
+
if (migState.status === 'surveyed' && step === 1) {
|
|
2094
|
+
migState.status = 'in-progress';
|
|
2095
|
+
migState.pages[0].status = 'in-progress';
|
|
2096
|
+
migState.pages[0].startedAt = new Date().toISOString();
|
|
2097
|
+
writeMigrationState(root, migState);
|
|
2098
|
+
}
|
|
2099
|
+
const stepFns = {
|
|
2100
|
+
1: printMigrateStep1,
|
|
2101
|
+
2: printMigrateStep2,
|
|
2102
|
+
3: printMigrateStep3,
|
|
2103
|
+
4: printMigrateStep4,
|
|
2104
|
+
5: printMigrateStep5,
|
|
2105
|
+
6: printMigrateStep6,
|
|
2106
|
+
7: printMigrateStep7,
|
|
2107
|
+
8: printMigrateStep8,
|
|
2108
|
+
9: printMigrateStep9,
|
|
2109
|
+
10: printMigrateStep10,
|
|
2110
|
+
};
|
|
2111
|
+
stepFns[step](root);
|
|
1417
2112
|
}
|
|
1418
2113
|
// ─── Step 14: Commit ─────────────────────────────────────────────────
|
|
1419
2114
|
function printStep14(root, feature) {
|
|
@@ -1521,26 +2216,54 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
1521
2216
|
}
|
|
1522
2217
|
let glossaryEntries;
|
|
1523
2218
|
try {
|
|
1524
|
-
|
|
2219
|
+
const parsed = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
2220
|
+
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
1525
2221
|
}
|
|
1526
2222
|
catch {
|
|
1527
2223
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
1528
2224
|
process.exit(1);
|
|
1529
2225
|
}
|
|
1530
|
-
if (
|
|
2226
|
+
if (glossaryEntries.length === 0) {
|
|
1531
2227
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
1532
2228
|
process.exit(1);
|
|
1533
2229
|
}
|
|
1534
2230
|
const filePaths = glossaryEntries.map((e) => e.filePath);
|
|
1535
2231
|
// Include page files so pages get analyzed as entities too.
|
|
1536
2232
|
// This allows the import graph to map page names to their dependencies.
|
|
2233
|
+
// Use allFiles (not map values) to include ALL pages — multiple routes
|
|
2234
|
+
// can share a page name (e.g., /feedback/[id] and /feedback/new both
|
|
2235
|
+
// map to "Feedback") but each needs its own entity analysis.
|
|
1537
2236
|
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
1538
|
-
const
|
|
1539
|
-
for (const pageFile of
|
|
2237
|
+
const { allFiles: allPageFiles } = scanPageFilePaths(root);
|
|
2238
|
+
for (const pageFile of allPageFiles) {
|
|
1540
2239
|
if (!filePaths.includes(pageFile)) {
|
|
1541
2240
|
filePaths.push(pageFile);
|
|
1542
2241
|
}
|
|
1543
2242
|
}
|
|
2243
|
+
// Include file paths from editor scenarios (both component_path and
|
|
2244
|
+
// page_file_path) so that all components and pages with scenarios get
|
|
2245
|
+
// entities — critical for non-Next.js apps where scanPageFilePaths
|
|
2246
|
+
// doesn't find page.tsx files.
|
|
2247
|
+
try {
|
|
2248
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2249
|
+
const db = getDatabase();
|
|
2250
|
+
const scenarioFiles = await db
|
|
2251
|
+
.selectFrom('editor_scenarios')
|
|
2252
|
+
.select(['component_path', 'page_file_path'])
|
|
2253
|
+
.distinct()
|
|
2254
|
+
.execute();
|
|
2255
|
+
for (const row of scenarioFiles) {
|
|
2256
|
+
const r = row;
|
|
2257
|
+
for (const fp of [r.component_path, r.page_file_path]) {
|
|
2258
|
+
if (fp && !filePaths.includes(fp)) {
|
|
2259
|
+
filePaths.push(fp);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
catch {
|
|
2265
|
+
// Non-fatal — scenario file paths just won't be included
|
|
2266
|
+
}
|
|
1544
2267
|
const progress = new ProgressReporter();
|
|
1545
2268
|
// Run data-structure-only analysis for all entities (glossary + pages)
|
|
1546
2269
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
@@ -1630,6 +2353,24 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
1630
2353
|
});
|
|
1631
2354
|
console.log(summary || JSON.stringify({ imports, entities: entityData }));
|
|
1632
2355
|
}
|
|
2356
|
+
// Backfill entity_sha on scenarios registered before entities existed.
|
|
2357
|
+
// This fixes the "missing data" UI state when scenarios were registered
|
|
2358
|
+
// while analyze-imports was still running in the background.
|
|
2359
|
+
try {
|
|
2360
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2361
|
+
const db = getDatabase();
|
|
2362
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, latestEntities.map((e) => ({
|
|
2363
|
+
sha: e.sha,
|
|
2364
|
+
name: e.name,
|
|
2365
|
+
filePath: e.filePath || '',
|
|
2366
|
+
})));
|
|
2367
|
+
if (backfillResult.updated > 0 && !options.silent) {
|
|
2368
|
+
console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
catch {
|
|
2372
|
+
/* non-fatal */
|
|
2373
|
+
}
|
|
1633
2374
|
}
|
|
1634
2375
|
// ─── Validate-seed subcommand ─────────────────────────────────────────
|
|
1635
2376
|
/**
|
|
@@ -1668,6 +2409,24 @@ function handleValidateSeed(jsonArg) {
|
|
|
1668
2409
|
: 0;
|
|
1669
2410
|
console.log(chalk.dim(` ${table}: ${rows} row(s)`));
|
|
1670
2411
|
}
|
|
2412
|
+
// Check seed keys against Prisma schema if available
|
|
2413
|
+
const schemaPath = path.join(root, 'prisma', 'schema.prisma');
|
|
2414
|
+
try {
|
|
2415
|
+
if (fs.existsSync(schemaPath)) {
|
|
2416
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
2417
|
+
const warnings = validateSeedKeysAgainstPrisma(tables, schemaContent);
|
|
2418
|
+
if (warnings.length > 0) {
|
|
2419
|
+
console.log();
|
|
2420
|
+
console.log(chalk.yellow('Prisma model name warnings:'));
|
|
2421
|
+
for (const w of warnings) {
|
|
2422
|
+
console.log(chalk.yellow(` ⚠ ${w}`));
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
catch {
|
|
2428
|
+
// Schema not readable — skip Prisma validation
|
|
2429
|
+
}
|
|
1671
2430
|
}
|
|
1672
2431
|
else {
|
|
1673
2432
|
console.error(chalk.red('Seed data validation failed:'));
|
|
@@ -1677,6 +2436,27 @@ function handleValidateSeed(jsonArg) {
|
|
|
1677
2436
|
process.exit(1);
|
|
1678
2437
|
}
|
|
1679
2438
|
}
|
|
2439
|
+
// ─── Delete subcommand ────────────────────────────────────────────────
|
|
2440
|
+
/**
|
|
2441
|
+
* `codeyam editor delete <scenarioId>`
|
|
2442
|
+
*
|
|
2443
|
+
* Delete a scenario by ID via the running editor server.
|
|
2444
|
+
*/
|
|
2445
|
+
async function handleDelete(scenarioId) {
|
|
2446
|
+
if (!scenarioId) {
|
|
2447
|
+
console.error(chalk.red('Error: Missing scenario ID. Usage: codeyam editor delete <scenarioId>'));
|
|
2448
|
+
process.exit(1);
|
|
2449
|
+
}
|
|
2450
|
+
const port = getServerPort();
|
|
2451
|
+
const result = await deleteScenarioViaCli(scenarioId, port);
|
|
2452
|
+
if (result.success) {
|
|
2453
|
+
console.log(chalk.green(result.message));
|
|
2454
|
+
}
|
|
2455
|
+
else {
|
|
2456
|
+
console.error(chalk.red(result.message));
|
|
2457
|
+
process.exit(1);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
1680
2460
|
// ─── Isolate subcommand ───────────────────────────────────────────────
|
|
1681
2461
|
/**
|
|
1682
2462
|
* `codeyam editor isolate "StarRating CategoryBadge DrinkCard"`
|
|
@@ -2151,23 +2931,106 @@ function handleChange(feature) {
|
|
|
2151
2931
|
}
|
|
2152
2932
|
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2153
2933
|
/**
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
* Used as a hard gate before steps 8+.
|
|
2934
|
+
* Fetch the audit result from the server.
|
|
2935
|
+
* Returns null if the server is unreachable.
|
|
2157
2936
|
*/
|
|
2158
|
-
async function
|
|
2937
|
+
async function fetchAuditResult() {
|
|
2159
2938
|
const port = getServerPort();
|
|
2160
2939
|
try {
|
|
2161
2940
|
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
2162
2941
|
if (!res.ok)
|
|
2163
|
-
return
|
|
2164
|
-
|
|
2165
|
-
return data?.summary?.allPassing === true;
|
|
2942
|
+
return null;
|
|
2943
|
+
return await res.json();
|
|
2166
2944
|
}
|
|
2167
2945
|
catch {
|
|
2168
|
-
|
|
2946
|
+
return null;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
2951
|
+
* false if any issues remain or the server is unreachable.
|
|
2952
|
+
* Used as a hard gate before steps 8+.
|
|
2953
|
+
*
|
|
2954
|
+
* Auto-runs analyze-imports if the only failure is incomplete entities,
|
|
2955
|
+
* then re-checks once.
|
|
2956
|
+
*/
|
|
2957
|
+
async function checkAuditGate() {
|
|
2958
|
+
const data = await fetchAuditResult();
|
|
2959
|
+
if (!data)
|
|
2960
|
+
return true; // Server unreachable — don't block
|
|
2961
|
+
if (data.summary?.allPassing === true)
|
|
2169
2962
|
return true;
|
|
2963
|
+
// If incomplete entities are the only issue, auto-fix with analyze-imports (once)
|
|
2964
|
+
const { isAutoRemediable } = await import('../utils/editorAudit.js');
|
|
2965
|
+
if (isAutoRemediable(data.summary, false)) {
|
|
2966
|
+
try {
|
|
2967
|
+
await handleAnalyzeImports({ silent: true });
|
|
2968
|
+
}
|
|
2969
|
+
catch {
|
|
2970
|
+
return false;
|
|
2971
|
+
}
|
|
2972
|
+
// Re-check after fix
|
|
2973
|
+
const retry = await fetchAuditResult();
|
|
2974
|
+
if (retry?.summary?.allPassing === true)
|
|
2975
|
+
return true;
|
|
2976
|
+
}
|
|
2977
|
+
// Print specific failures so Claude knows what to fix without running audit separately
|
|
2978
|
+
printAuditGateFailures(data);
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Print a concise summary of audit failures for the gate block message.
|
|
2983
|
+
* This gives Claude immediate context about what to fix without needing
|
|
2984
|
+
* to run `codeyam editor audit` as a separate step.
|
|
2985
|
+
*/
|
|
2986
|
+
function printAuditGateFailures(data) {
|
|
2987
|
+
const s = data.summary;
|
|
2988
|
+
if (!s)
|
|
2989
|
+
return;
|
|
2990
|
+
const issues = [];
|
|
2991
|
+
if (s.componentsMissing > 0)
|
|
2992
|
+
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
2993
|
+
if (s.componentsWithErrors > 0)
|
|
2994
|
+
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
2995
|
+
if (s.functionsMissing > 0)
|
|
2996
|
+
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
2997
|
+
if (s.functionsFailing > 0)
|
|
2998
|
+
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
2999
|
+
if (s.functionsNameMismatch > 0)
|
|
3000
|
+
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
3001
|
+
if (s.missingFromGlossary > 0)
|
|
3002
|
+
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3003
|
+
if (s.incompleteEntities > 0)
|
|
3004
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3005
|
+
if (s.miscategorizedScenarios > 0)
|
|
3006
|
+
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3007
|
+
if (issues.length > 0) {
|
|
3008
|
+
console.error(chalk.yellow('\nAudit failures:'));
|
|
3009
|
+
for (const issue of issues) {
|
|
3010
|
+
console.error(chalk.yellow(` • ${issue}`));
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
// Surface client error details inline — these are the most common cause of
|
|
3014
|
+
// Claude looping because the errors require code fixes, not audit re-runs.
|
|
3015
|
+
if (data.components) {
|
|
3016
|
+
const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
|
|
3017
|
+
if (withErrors.length > 0) {
|
|
3018
|
+
console.error(chalk.yellow('\nClient errors found:'));
|
|
3019
|
+
for (const c of withErrors) {
|
|
3020
|
+
console.error(chalk.red(` ${c.name}:`));
|
|
3021
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3022
|
+
console.error(chalk.red(` → ${err}`));
|
|
3023
|
+
}
|
|
3024
|
+
if (c.clientErrors.length > 3) {
|
|
3025
|
+
console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3029
|
+
console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
|
|
3030
|
+
console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
|
|
3031
|
+
}
|
|
2170
3032
|
}
|
|
3033
|
+
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
2171
3034
|
}
|
|
2172
3035
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
2173
3036
|
/**
|
|
@@ -2178,22 +3041,38 @@ async function checkAuditGate() {
|
|
|
2178
3041
|
* have test files. Exits with code 1 if anything is missing.
|
|
2179
3042
|
*/
|
|
2180
3043
|
async function handleAudit() {
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
let data;
|
|
2184
|
-
try {
|
|
2185
|
-
const res = await fetch(url);
|
|
2186
|
-
if (!res.ok) {
|
|
2187
|
-
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
2188
|
-
process.exit(1);
|
|
2189
|
-
}
|
|
2190
|
-
data = await res.json();
|
|
2191
|
-
}
|
|
2192
|
-
catch (err) {
|
|
3044
|
+
let data = await fetchAuditResult();
|
|
3045
|
+
if (!data) {
|
|
2193
3046
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2194
|
-
console.error(chalk.dim(` ${err.message}`));
|
|
2195
3047
|
process.exit(1);
|
|
2196
3048
|
}
|
|
3049
|
+
// Auto-fix incomplete entities — but only once.
|
|
3050
|
+
// If analyze-imports runs and entities are STILL incomplete, report clearly
|
|
3051
|
+
// instead of retrying. The analysis may have errors (e.g., stale entity
|
|
3052
|
+
// references from refactoring) that can't be fixed by re-running.
|
|
3053
|
+
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3054
|
+
let autoRemediationFailed = false;
|
|
3055
|
+
if (incompleteBeforeFix.length > 0) {
|
|
3056
|
+
console.log(chalk.dim(`Running import analysis for ${incompleteBeforeFix.length} incomplete entit${incompleteBeforeFix.length !== 1 ? 'ies' : 'y'}...`));
|
|
3057
|
+
try {
|
|
3058
|
+
await handleAnalyzeImports({ silent: true });
|
|
3059
|
+
}
|
|
3060
|
+
catch {
|
|
3061
|
+
// Fall through — the audit will still report them as incomplete
|
|
3062
|
+
}
|
|
3063
|
+
// Re-fetch audit results after the fix
|
|
3064
|
+
data = await fetchAuditResult();
|
|
3065
|
+
if (!data) {
|
|
3066
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analyze-imports.'));
|
|
3067
|
+
process.exit(1);
|
|
3068
|
+
}
|
|
3069
|
+
// If entities are still incomplete after running analyze-imports,
|
|
3070
|
+
// flag it so we can show a clear message instead of looping
|
|
3071
|
+
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3072
|
+
if (incompleteAfterFix.length > 0) {
|
|
3073
|
+
autoRemediationFailed = true;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
2197
3076
|
const { components, functions, summary } = data;
|
|
2198
3077
|
console.log();
|
|
2199
3078
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
@@ -2221,6 +3100,14 @@ async function handleAudit() {
|
|
|
2221
3100
|
if (c.clientErrors.length > 3) {
|
|
2222
3101
|
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
2223
3102
|
}
|
|
3103
|
+
// Detect browser API errors and provide actionable guidance
|
|
3104
|
+
const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
|
|
3105
|
+
const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
|
|
3106
|
+
if (hasBrowserApiErrors) {
|
|
3107
|
+
console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
|
|
3108
|
+
console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
|
|
3109
|
+
console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
|
|
3110
|
+
}
|
|
2224
3111
|
}
|
|
2225
3112
|
}
|
|
2226
3113
|
console.log();
|
|
@@ -2252,6 +3139,53 @@ async function handleAudit() {
|
|
|
2252
3139
|
}
|
|
2253
3140
|
console.log();
|
|
2254
3141
|
}
|
|
3142
|
+
// Missing from glossary
|
|
3143
|
+
const missingFromGlossary = data.missingFromGlossary || [];
|
|
3144
|
+
if (missingFromGlossary.length > 0) {
|
|
3145
|
+
console.log(chalk.bold('Missing from glossary:'));
|
|
3146
|
+
for (const m of missingFromGlossary) {
|
|
3147
|
+
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3148
|
+
}
|
|
3149
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3150
|
+
console.log();
|
|
3151
|
+
}
|
|
3152
|
+
// Incomplete entities (scenarios without analyses)
|
|
3153
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
3154
|
+
if (incompleteEntities.length > 0) {
|
|
3155
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3156
|
+
for (const e of incompleteEntities) {
|
|
3157
|
+
console.log(` ${chalk.red('✗')} ${e.name} — ${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''} but entity not analyzed`);
|
|
3158
|
+
}
|
|
3159
|
+
if (autoRemediationFailed) {
|
|
3160
|
+
console.log(chalk.red(' analyze-imports was run automatically but these entities are STILL incomplete.'));
|
|
3161
|
+
console.log(chalk.red(' DO NOT re-run analyze-imports or the audit in a loop — the result will be the same.'));
|
|
3162
|
+
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3163
|
+
console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
|
|
3164
|
+
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3165
|
+
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3166
|
+
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3167
|
+
console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
|
|
3168
|
+
}
|
|
3169
|
+
else {
|
|
3170
|
+
console.log(chalk.yellow(' Run `codeyam editor analyze-imports` to analyze these entities'));
|
|
3171
|
+
}
|
|
3172
|
+
console.log();
|
|
3173
|
+
}
|
|
3174
|
+
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
3175
|
+
const miscategorizedScenarios = data.miscategorizedScenarios || [];
|
|
3176
|
+
if (miscategorizedScenarios.length > 0) {
|
|
3177
|
+
console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
|
|
3178
|
+
for (const m of miscategorizedScenarios) {
|
|
3179
|
+
console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
|
|
3180
|
+
for (const name of m.scenarioNames) {
|
|
3181
|
+
console.log(chalk.dim(` "${name}"`));
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
|
|
3185
|
+
console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
|
|
3186
|
+
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
3187
|
+
console.log();
|
|
3188
|
+
}
|
|
2255
3189
|
// Summary
|
|
2256
3190
|
const allOk = summary.allPassing;
|
|
2257
3191
|
if (allOk) {
|
|
@@ -2275,6 +3209,15 @@ async function handleAudit() {
|
|
|
2275
3209
|
if (summary.functionsNameMismatch > 0) {
|
|
2276
3210
|
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
2277
3211
|
}
|
|
3212
|
+
if (summary.missingFromGlossary > 0) {
|
|
3213
|
+
parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
|
|
3214
|
+
}
|
|
3215
|
+
if (summary.incompleteEntities > 0) {
|
|
3216
|
+
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3217
|
+
}
|
|
3218
|
+
if (summary.miscategorizedScenarios > 0) {
|
|
3219
|
+
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3220
|
+
}
|
|
2278
3221
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
2279
3222
|
}
|
|
2280
3223
|
console.log();
|
|
@@ -2392,6 +3335,30 @@ async function handleScenarios() {
|
|
|
2392
3335
|
}
|
|
2393
3336
|
// ─── Scenario Coverage subcommand ───────────────────────────────────
|
|
2394
3337
|
async function handleScenarioCoverage() {
|
|
3338
|
+
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3339
|
+
try {
|
|
3340
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3341
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
3342
|
+
const db = getDatabase();
|
|
3343
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3344
|
+
if (backfillCount > 0) {
|
|
3345
|
+
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3346
|
+
await handleAnalyzeImports({ silent: true });
|
|
3347
|
+
// Run backfill after analysis
|
|
3348
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
3349
|
+
const entities = await loadEntities({});
|
|
3350
|
+
if (entities && entities.length > 0) {
|
|
3351
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3352
|
+
sha: e.sha,
|
|
3353
|
+
name: e.name,
|
|
3354
|
+
filePath: e.filePath || '',
|
|
3355
|
+
})));
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
catch {
|
|
3360
|
+
// Non-fatal — proceed with coverage check regardless
|
|
3361
|
+
}
|
|
2395
3362
|
const port = getServerPort();
|
|
2396
3363
|
const url = `http://localhost:${port}/api/editor-scenario-coverage`;
|
|
2397
3364
|
let data;
|
|
@@ -2539,7 +3506,20 @@ async function handleTemplate() {
|
|
|
2539
3506
|
// Config parse error is non-fatal
|
|
2540
3507
|
}
|
|
2541
3508
|
}
|
|
2542
|
-
// 5b.
|
|
3509
|
+
// 5b. Mark the project as template-scaffolded so migration detection
|
|
3510
|
+
// doesn't treat it as a pre-existing project that needs migration.
|
|
3511
|
+
const now = new Date().toISOString();
|
|
3512
|
+
const existingState = readState(root);
|
|
3513
|
+
writeState(root, {
|
|
3514
|
+
feature: '',
|
|
3515
|
+
step: 0,
|
|
3516
|
+
label: '',
|
|
3517
|
+
startedAt: now,
|
|
3518
|
+
featureStartedAt: now,
|
|
3519
|
+
...existingState,
|
|
3520
|
+
scaffolded: true,
|
|
3521
|
+
});
|
|
3522
|
+
// 5c. Write a fresh prototypeId so the proxy clears stale localStorage
|
|
2543
3523
|
const activeScenarioPath = path.join(root, '.codeyam', 'active-scenario.json');
|
|
2544
3524
|
fs.writeFileSync(activeScenarioPath, JSON.stringify({ prototypeId: Date.now().toString() }), 'utf-8');
|
|
2545
3525
|
// 6. Trigger editor-refresh so the server picks up the new project
|
|
@@ -2917,7 +3897,9 @@ const editorCommand = {
|
|
|
2917
3897
|
describe: 'Debug: output directory for the bundle',
|
|
2918
3898
|
});
|
|
2919
3899
|
}
|
|
2920
|
-
|
|
3900
|
+
// Allow extra positional args for subcommands like `isolate A B C`
|
|
3901
|
+
// without yargs rejecting them as unknown.
|
|
3902
|
+
return builder.strict(false);
|
|
2921
3903
|
},
|
|
2922
3904
|
handler: async (argv) => {
|
|
2923
3905
|
const root = getProjectRoot();
|
|
@@ -2996,14 +3978,16 @@ const editorCommand = {
|
|
|
2996
3978
|
await handleValidateSeed(argv.json || '');
|
|
2997
3979
|
return;
|
|
2998
3980
|
}
|
|
3981
|
+
// Subcommand: codeyam editor delete <scenarioId>
|
|
3982
|
+
if (argv.step === 'delete') {
|
|
3983
|
+
await handleDelete(argv.json || '');
|
|
3984
|
+
return;
|
|
3985
|
+
}
|
|
2999
3986
|
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
3987
|
+
// Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
3000
3988
|
if (argv.step === 'isolate') {
|
|
3001
|
-
const
|
|
3002
|
-
|
|
3003
|
-
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
3004
|
-
// Collect any extra positional args (yargs puts them in argv._)
|
|
3005
|
-
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
3006
|
-
names.push(...extras);
|
|
3989
|
+
const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
|
|
3990
|
+
const names = parseIsolateArgs(argv.json, argv._);
|
|
3007
3991
|
handleIsolate(names);
|
|
3008
3992
|
return;
|
|
3009
3993
|
}
|
|
@@ -3026,6 +4010,11 @@ const editorCommand = {
|
|
|
3026
4010
|
await handleEditorDebug(argv);
|
|
3027
4011
|
return;
|
|
3028
4012
|
}
|
|
4013
|
+
// Subcommand: codeyam editor migrate [subArg]
|
|
4014
|
+
if (argv.step === 'migrate') {
|
|
4015
|
+
handleMigrateCommand(root, argv.json || undefined);
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
3029
4018
|
// Subcommand: codeyam editor steps — show setup or cycle overview
|
|
3030
4019
|
if (argv.step === 'steps') {
|
|
3031
4020
|
if (!hasProject(root)) {
|
|
@@ -3095,6 +4084,9 @@ const editorCommand = {
|
|
|
3095
4084
|
'screenshot_path',
|
|
3096
4085
|
'viewport_width',
|
|
3097
4086
|
'viewport_height',
|
|
4087
|
+
'dimensions',
|
|
4088
|
+
'screenshot_paths',
|
|
4089
|
+
'page_file_path',
|
|
3098
4090
|
'created_at',
|
|
3099
4091
|
'updated_at',
|
|
3100
4092
|
])
|
|
@@ -3183,6 +4175,41 @@ const editorCommand = {
|
|
|
3183
4175
|
catch {
|
|
3184
4176
|
// Non-fatal — migration failure shouldn't block editor startup
|
|
3185
4177
|
}
|
|
4178
|
+
// Auto-seed on fresh clone: if no scenario has ever been activated
|
|
4179
|
+
// (active-scenario.json doesn't exist), seed the application database
|
|
4180
|
+
// with the first application scenario that has seed data.
|
|
4181
|
+
const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
|
|
4182
|
+
if (!fs.existsSync(activeScenarioPath)) {
|
|
4183
|
+
try {
|
|
4184
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4185
|
+
const seedDb = getDb();
|
|
4186
|
+
const appScenario = await seedDb
|
|
4187
|
+
.selectFrom('editor_scenarios')
|
|
4188
|
+
.select(['id', 'name', 'type'])
|
|
4189
|
+
.where('project_id', '=', project.id)
|
|
4190
|
+
.where('type', '=', 'application')
|
|
4191
|
+
.orderBy('created_at', 'asc')
|
|
4192
|
+
.executeTakeFirst();
|
|
4193
|
+
if (appScenario) {
|
|
4194
|
+
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4195
|
+
if (fs.existsSync(seedFile)) {
|
|
4196
|
+
const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
|
|
4197
|
+
const seedResult = await switchActiveScenario({
|
|
4198
|
+
scenarioId: appScenario.id,
|
|
4199
|
+
scenarioName: appScenario.name || undefined,
|
|
4200
|
+
scenarioType: appScenario.type || undefined,
|
|
4201
|
+
projectRoot,
|
|
4202
|
+
});
|
|
4203
|
+
if (seedResult.seedResult?.success) {
|
|
4204
|
+
console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
catch {
|
|
4210
|
+
// Non-fatal — auto-seed is best-effort
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
3186
4213
|
// `codeyam editor` (no step) always implies editor mode.
|
|
3187
4214
|
// The empty-folder heuristic is no longer needed here — running this
|
|
3188
4215
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -3248,11 +4275,77 @@ const editorCommand = {
|
|
|
3248
4275
|
project,
|
|
3249
4276
|
branch,
|
|
3250
4277
|
});
|
|
3251
|
-
// Build import graph
|
|
4278
|
+
// Build import graph if glossary exists but entities are missing.
|
|
4279
|
+
// Runs on first startup (no entities at all) AND when page files or
|
|
4280
|
+
// scenario component files lack entity coverage.
|
|
3252
4281
|
const glossaryPath = path.join(projectRoot, '.codeyam', 'glossary.json');
|
|
3253
4282
|
if (fs.existsSync(glossaryPath)) {
|
|
4283
|
+
let needsAnalysis = false;
|
|
3254
4284
|
const entities = await loadEntities({});
|
|
3255
4285
|
if (!entities || entities.length === 0) {
|
|
4286
|
+
needsAnalysis = true;
|
|
4287
|
+
}
|
|
4288
|
+
else {
|
|
4289
|
+
const entityFilePaths = new Set(entities.map((e) => e.filePath));
|
|
4290
|
+
// Check if any page files are missing entities (Next.js apps)
|
|
4291
|
+
try {
|
|
4292
|
+
const { scanPageFilePaths } = await import('../utils/entityChangeStatus.server.js');
|
|
4293
|
+
const { allFiles } = scanPageFilePaths(projectRoot);
|
|
4294
|
+
const pageFiles = allFiles.filter((f) => f.endsWith('/page.tsx') || f.endsWith('/page.js'));
|
|
4295
|
+
const missingPages = pageFiles.filter((f) => !entityFilePaths.has(f));
|
|
4296
|
+
if (missingPages.length > 0) {
|
|
4297
|
+
console.log(chalk.dim(` Found ${missingPages.length} page(s) without entity analysis — running import analysis...`));
|
|
4298
|
+
needsAnalysis = true;
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
catch {
|
|
4302
|
+
// Non-fatal — page file check failed
|
|
4303
|
+
}
|
|
4304
|
+
// Check if any scenario files (component_path or page_file_path)
|
|
4305
|
+
// are missing entities — covers non-Next.js apps
|
|
4306
|
+
if (!needsAnalysis) {
|
|
4307
|
+
try {
|
|
4308
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4309
|
+
const db = getDatabase();
|
|
4310
|
+
const scenarioFiles = await db
|
|
4311
|
+
.selectFrom('editor_scenarios')
|
|
4312
|
+
.select(['component_path', 'page_file_path'])
|
|
4313
|
+
.where('project_id', '=', project.id)
|
|
4314
|
+
.distinct()
|
|
4315
|
+
.execute();
|
|
4316
|
+
const missingCount = scenarioFiles.filter((row) => {
|
|
4317
|
+
const cp = row.component_path;
|
|
4318
|
+
const pfp = row.page_file_path;
|
|
4319
|
+
return ((cp && !entityFilePaths.has(cp)) ||
|
|
4320
|
+
(pfp && !entityFilePaths.has(pfp)));
|
|
4321
|
+
}).length;
|
|
4322
|
+
if (missingCount > 0) {
|
|
4323
|
+
console.log(chalk.dim(` Found ${missingCount} scenario file(s) without entity analysis — running import analysis...`));
|
|
4324
|
+
needsAnalysis = true;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
catch {
|
|
4328
|
+
// Non-fatal
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
// Check if any scenarios have null entity_sha with file paths — heal on startup
|
|
4332
|
+
if (!needsAnalysis) {
|
|
4333
|
+
try {
|
|
4334
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4335
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4336
|
+
const db = getDatabase();
|
|
4337
|
+
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4338
|
+
if (backfillCount > 0) {
|
|
4339
|
+
console.log(chalk.dim(` Found ${backfillCount} scenario(s) with unlinked entities — running import analysis...`));
|
|
4340
|
+
needsAnalysis = true;
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
catch {
|
|
4344
|
+
// Non-fatal
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
if (needsAnalysis) {
|
|
3256
4349
|
try {
|
|
3257
4350
|
await handleAnalyzeImports({ silent: true });
|
|
3258
4351
|
}
|
|
@@ -3261,6 +4354,69 @@ const editorCommand = {
|
|
|
3261
4354
|
}
|
|
3262
4355
|
}
|
|
3263
4356
|
}
|
|
4357
|
+
// Backfill page_file_path for application scenarios that have a URL but
|
|
4358
|
+
// no page_file_path. This resolves the file from the URL using Next.js
|
|
4359
|
+
// routing conventions, enabling syncScenarioEntityShas to link them to
|
|
4360
|
+
// entities. Covers fresh clones where JSON files lack pageFilePath.
|
|
4361
|
+
try {
|
|
4362
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4363
|
+
const pfpDb = getDb();
|
|
4364
|
+
const unresolved = await pfpDb
|
|
4365
|
+
.selectFrom('editor_scenarios')
|
|
4366
|
+
.select(['id', 'url'])
|
|
4367
|
+
.where('project_id', '=', project.id)
|
|
4368
|
+
.where('component_name', 'is', null)
|
|
4369
|
+
.where('page_file_path', 'is', null)
|
|
4370
|
+
.where('url', 'is not', null)
|
|
4371
|
+
.execute();
|
|
4372
|
+
if (unresolved.length > 0) {
|
|
4373
|
+
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4374
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
4375
|
+
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4376
|
+
let pfpResolved = 0;
|
|
4377
|
+
for (const row of unresolved) {
|
|
4378
|
+
const r = row;
|
|
4379
|
+
if (!r.url)
|
|
4380
|
+
continue;
|
|
4381
|
+
const matched = matchUrlToPageFile(r.url, pfpFiles);
|
|
4382
|
+
if (matched) {
|
|
4383
|
+
await pfpDb
|
|
4384
|
+
.updateTable('editor_scenarios')
|
|
4385
|
+
.set({ page_file_path: matched })
|
|
4386
|
+
.where('id', '=', r.id)
|
|
4387
|
+
.execute();
|
|
4388
|
+
pfpResolved++;
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
if (pfpResolved > 0) {
|
|
4392
|
+
console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
catch {
|
|
4397
|
+
/* Non-fatal — page_file_path backfill from URL */
|
|
4398
|
+
}
|
|
4399
|
+
// Backfill entity_sha on scenarios that were synced before entities existed.
|
|
4400
|
+
// This runs independently of analyze-imports so fresh clones and file-synced
|
|
4401
|
+
// scenarios get linked to their entities even when auto-analyze doesn't trigger.
|
|
4402
|
+
try {
|
|
4403
|
+
const entities = await loadEntities({});
|
|
4404
|
+
if (entities && entities.length > 0) {
|
|
4405
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4406
|
+
const db = getDatabase();
|
|
4407
|
+
const result = await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4408
|
+
sha: e.sha,
|
|
4409
|
+
name: e.name,
|
|
4410
|
+
filePath: e.filePath || '',
|
|
4411
|
+
})));
|
|
4412
|
+
if (result.updated > 0) {
|
|
4413
|
+
console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
catch {
|
|
4418
|
+
/* Non-fatal */
|
|
4419
|
+
}
|
|
3264
4420
|
console.log();
|
|
3265
4421
|
console.log(` Dashboard: ${url}`);
|
|
3266
4422
|
console.log(' Run "codeyam --help" for all commands');
|
|
@@ -3274,7 +4430,9 @@ const editorCommand = {
|
|
|
3274
4430
|
: process.platform === 'win32'
|
|
3275
4431
|
? 'start ""'
|
|
3276
4432
|
: 'xdg-open';
|
|
3277
|
-
execSync(`${openCommand} "${url}/editor"`, {
|
|
4433
|
+
execSync(`${openCommand} "${url}/editor"`, {
|
|
4434
|
+
stdio: 'ignore',
|
|
4435
|
+
});
|
|
3278
4436
|
}
|
|
3279
4437
|
catch {
|
|
3280
4438
|
// Silently fail if open command doesn't work
|
|
@@ -3344,8 +4502,9 @@ const editorCommand = {
|
|
|
3344
4502
|
if (step >= 8) {
|
|
3345
4503
|
const auditOk = await checkAuditGate();
|
|
3346
4504
|
if (!auditOk) {
|
|
3347
|
-
|
|
3348
|
-
console.error(chalk.
|
|
4505
|
+
// checkAuditGate() already printed specific failure details above
|
|
4506
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4507
|
+
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
3349
4508
|
process.exit(1);
|
|
3350
4509
|
}
|
|
3351
4510
|
}
|