@codeyam/codeyam-cli 0.1.0-staging.b8b17a5 → 0.1.0-staging.b8ee127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +4 -4
- package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
- package/analyzer-template/packages/analyze/index.ts +4 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
- package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
- package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
- package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
- package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
- package/analyzer-template/packages/aws/package.json +6 -6
- package/analyzer-template/packages/database/package.json +2 -2
- package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +25 -15
- package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +7 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
- package/analyzer-template/project/analyzeFileEntities.ts +26 -0
- package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
- package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
- package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
- package/codeyam-cli/src/cli.js +15 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +9 -9
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +38 -3
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +2207 -467
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
- package/codeyam-cli/src/commands/init.js +20 -0
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/data/designSystems.js +27 -0
- package/codeyam-cli/src/data/designSystems.js.map +1 -0
- package/codeyam-cli/src/data/techStacks.js +1 -1
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js +44 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +2232 -52
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +66 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +398 -0
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +190 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +249 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +134 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +266 -2
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js +177 -0
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js +16 -1
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +57 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +180 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/analysisRunner.js +36 -7
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +11 -1
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/backgroundServer.js +1 -1
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
- package/codeyam-cli/src/utils/editorApi.js +16 -0
- package/codeyam-cli/src/utils/editorApi.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +504 -43
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorGuard.js +36 -0
- package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorRecapture.js +109 -0
- package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
- package/codeyam-cli/src/utils/editorRoadmap.js +301 -0
- package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +39 -2
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +141 -7
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +69 -16
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +31 -3
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
- package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +32 -6
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
- package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
- package/codeyam-cli/src/utils/queue/job.js +35 -6
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
- package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js +4 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +66 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/screenshotHash.js +26 -0
- package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +9 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/testResultCache.js +53 -0
- package/codeyam-cli/src/utils/testResultCache.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.js +81 -0
- package/codeyam-cli/src/utils/testResultCache.server.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js +187 -0
- package/codeyam-cli/src/utils/testResultCache.server.test.js.map +1 -0
- package/codeyam-cli/src/utils/testResultCache.test.js +230 -0
- package/codeyam-cli/src/utils/testResultCache.test.js.map +1 -0
- package/codeyam-cli/src/utils/testRunner.js +193 -1
- package/codeyam-cli/src/utils/testRunner.js.map +1 -1
- package/codeyam-cli/src/utils/webappDetection.js +4 -2
- package/codeyam-cli/src/utils/webappDetection.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js +99 -0
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +68 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +145 -11
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +190 -21
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +135 -0
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +22 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
- package/codeyam-cli/src/webserver/backgroundServer.js +42 -57
- package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CzTDWkF2.js → CopyButton-DTBZZfSk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BFbq6iFk.js → EntityItem-BxclONWq.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-B6OMi58N.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-DuYodzo1.js → InlineSpinner-ByaELMbv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CXo9EeCl.js → InteractivePreview-6WjVfhxX.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-DYCNb2It.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CZgY3sxX.js → LogViewer-C-9zQdXg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-BusrvT2F.js +36 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CnYYwRDw.js → ReportIssueModal-DQsceHVv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CDoF7ZpU.js → SafeScreenshot-DThcm_9M.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DrnfvaLL.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-CIil5-gb.js +34 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-DRKR9T0U.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-ClR-g3tY.js → _index-DnOgyseQ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DTH6ydEA.js → activity.(_tab)-DqM9hbNE.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-74hnHF59.js → addon-web-links-C58dYPwR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-B8CYhCO9.js → agent-transcripts-B8NCeOrm.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-recapture-stale-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-CLaoh4ac.js → book-open-BFSIqZgO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BZ2DZxbW.js → chevron-down-B9fDzFVh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CT4unAk-.js → circle-check-DLPObLUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-zK0B6Nu-.js → copy-DXEmO0TD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-DJB0YQJL.js → createLucideIcon-BwyFiRot.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CkXFP_i-.js → dev.empty-iRhRIFlp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-CsYVRiNH.js +147 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-C6fEYHrh.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BqAN7hyG.js → entity._sha._-Ce1s4OQ1.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-D1eikpe1.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-CJX6kkkV.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BhVjZhKg.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-_gzKltPN.js → entry.client-j1Vi0bco.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{files-CV_17tZS.js → files-kuny2Q_s.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-D-YXmMbR.js → git-DgCZPMie.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-Gp2o-NMc.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-CCrgCshv.js → index-BliGSSpl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-Blo6EK8G.js → index-SqjQKTdH.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-BsX0F-9C.js → index-vyrZD2g4.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{labs-Byazq8Pv.js → labs-c3yLxSEp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DVQ0oHR7.js → loader-circle-D-q28GLF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-ef0f624d.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-b-VmA2Vj.js → memory-CEWIUC4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{pause-DGcndCAa.js → pause-BP6fitdh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-Didv9PLi.js +80 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-C0Uw0bcK.js → search-BooqacKS.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{settings-OoNgHIfW.js → settings-BM0nbryO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-Bcemfu8a.js → simulations-ovy6FjRY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-BgMmG7R9.js → terminal-DHemCJIs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-Cs87hJYK.js → triangle-alert-D87ekDl8.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-BR3Rs7JY.js → useCustomSizes-Dk0Tciqg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-C8QvIe05.js +2 -0
- package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-BermyNU5.js → useReportContext-jkCytuYz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-a_QN_W9_.js → useToast-BgqkixU9.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BKMsxwqe.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-CHymws6l.js → index-CvuvIPEn.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-B3gVLAAJ.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-B4LxStYP.js +741 -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 +132 -7
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +65 -8
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +53 -0
- package/codeyam-cli/src/webserver/server.js +119 -14
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +174 -32
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +2 -0
- package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
- package/codeyam-cli/templates/codeyam-editor-reference.md +216 -0
- package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
- package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
- package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
- package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
- package/codeyam-cli/templates/editor-step-hook.py +93 -46
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
- package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
- package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
- package/codeyam-cli/templates/expo-react-native/app.json +11 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
- package/codeyam-cli/templates/expo-react-native/global.css +7 -0
- package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
- package/codeyam-cli/templates/expo-react-native/package.json +32 -16
- package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
- package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
- package/codeyam-cli/templates/seed-adapters/supabase.ts +133 -52
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +34 -1
- package/package.json +1 -1
- package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
- package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
- package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
- package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
- package/packages/ai/src/lib/astScopes/paths.js +12 -3
- package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
- package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
- package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/database/src/lib/loadAnalysis.js +7 -1
- package/packages/database/src/lib/loadAnalysis.js.map +1 -1
- package/packages/database/src/lib/loadEntity.js +5 -5
- package/packages/database/src/lib/loadEntity.js.map +1 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BBXArFPl.js +0 -43
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DYqG1D_d.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DggyRwOr.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-DRvOjyO3.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-f4212c17.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-F-k2uYj5.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BxxP_XF9.js +0 -2
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-if8kM_1Q.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/init-D3HkMDbI.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-DTCzJQiH.js +0 -551
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
|
@@ -13,35 +13,39 @@ import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
14
|
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
15
|
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
|
+
import { DESIGN_SYSTEMS } from "../data/designSystems.js";
|
|
16
17
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
18
|
import initCommand from "./init.js";
|
|
18
19
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
-
import { clearEditorState,
|
|
20
|
+
import { clearEditorState, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
21
|
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
22
|
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
22
23
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
23
24
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
25
|
+
import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
|
|
24
26
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
25
27
|
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
26
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
29
|
const __dirname = path.dirname(__filename);
|
|
28
30
|
const STEP_LABELS = {
|
|
29
31
|
1: 'Plan',
|
|
30
|
-
2: '
|
|
31
|
-
3: '
|
|
32
|
-
4: '
|
|
33
|
-
5: '
|
|
34
|
-
6: '
|
|
35
|
-
7: '
|
|
36
|
-
8: '
|
|
37
|
-
9: '
|
|
38
|
-
10: '
|
|
39
|
-
11: '
|
|
40
|
-
12: '
|
|
41
|
-
13: '
|
|
42
|
-
14: '
|
|
43
|
-
15: '
|
|
44
|
-
16: '
|
|
32
|
+
2: 'Prepare',
|
|
33
|
+
3: 'Prototype',
|
|
34
|
+
4: 'Verify Prototype',
|
|
35
|
+
5: 'Confirm',
|
|
36
|
+
6: 'Deconstruct',
|
|
37
|
+
7: 'Extract',
|
|
38
|
+
8: 'Glossary',
|
|
39
|
+
9: 'Analyze',
|
|
40
|
+
10: 'App Scenarios',
|
|
41
|
+
11: 'User Scenarios',
|
|
42
|
+
12: 'Verify',
|
|
43
|
+
13: 'Journal',
|
|
44
|
+
14: 'Review',
|
|
45
|
+
15: 'Present',
|
|
46
|
+
16: 'Commit',
|
|
47
|
+
17: 'Finalize',
|
|
48
|
+
18: 'Push',
|
|
45
49
|
};
|
|
46
50
|
const MIGRATION_STEP_LABELS = {
|
|
47
51
|
1: 'Survey',
|
|
@@ -119,11 +123,19 @@ function writeState(root, state) {
|
|
|
119
123
|
const dir = path.dirname(statePath);
|
|
120
124
|
fs.mkdirSync(dir, { recursive: true });
|
|
121
125
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
126
|
+
// Write task tracking file — marks that a task is expected for this step.
|
|
127
|
+
// The step hook sets taskCreated=true when it sees a TaskCreate tool call.
|
|
128
|
+
// Steps 1 and below don't require tasks (feature not named yet).
|
|
129
|
+
if (state.step && state.step >= 2) {
|
|
130
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
131
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
|
|
132
|
+
}
|
|
122
133
|
}
|
|
123
134
|
/**
|
|
124
135
|
* Clear the editor state (for starting a new feature).
|
|
125
|
-
* Does NOT clear the user prompt file —
|
|
126
|
-
*
|
|
136
|
+
* Does NOT clear the user prompt file — the prompt is cleared
|
|
137
|
+
* by the journal API after recording, and the hook captures
|
|
138
|
+
* a fresh prompt for the next feature on UserPromptSubmit.
|
|
127
139
|
*/
|
|
128
140
|
function clearState(root) {
|
|
129
141
|
clearEditorState(root);
|
|
@@ -177,6 +189,181 @@ function getProjectDimensions(root) {
|
|
|
177
189
|
return { defaultName: 'Desktop', names: [] };
|
|
178
190
|
}
|
|
179
191
|
}
|
|
192
|
+
function getTechStackContext(root) {
|
|
193
|
+
const state = readState(root);
|
|
194
|
+
const techStackId = state?.techStackId || '';
|
|
195
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
196
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
197
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
198
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
199
|
+
return {
|
|
200
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
201
|
+
isExpo,
|
|
202
|
+
isNextjs,
|
|
203
|
+
isChromeExt,
|
|
204
|
+
hasDatabase: isNextjs,
|
|
205
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
206
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
207
|
+
storageType: isExpo
|
|
208
|
+
? 'asyncStorage'
|
|
209
|
+
: isChromeExt
|
|
210
|
+
? 'chromeStorage'
|
|
211
|
+
: 'prisma',
|
|
212
|
+
routerImport: isExpo
|
|
213
|
+
? 'expo-router'
|
|
214
|
+
: isChromeExt
|
|
215
|
+
? 'react-router-dom'
|
|
216
|
+
: 'next/navigation',
|
|
217
|
+
componentPrimitives: isExpo
|
|
218
|
+
? '<View>, <Text>, <ScrollView>'
|
|
219
|
+
: '<div>, <span>, <h1>',
|
|
220
|
+
rawPrimitivesList: isExpo
|
|
221
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
222
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
223
|
+
patternsFile: isExpo
|
|
224
|
+
? 'MOBILE_SETUP.md'
|
|
225
|
+
: isChromeExt
|
|
226
|
+
? 'EXTENSION_SETUP.md'
|
|
227
|
+
: 'FEATURE_PATTERNS.md',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Returns a pre-populated tech stack for a given template ID.
|
|
232
|
+
* Written to .codeyam/config.json during `codeyam editor template`.
|
|
233
|
+
*/
|
|
234
|
+
function getTechStackForTemplate(templateId) {
|
|
235
|
+
switch (templateId) {
|
|
236
|
+
case 'nextjs-prisma-sqlite':
|
|
237
|
+
return {
|
|
238
|
+
languages: [
|
|
239
|
+
{
|
|
240
|
+
name: 'TypeScript',
|
|
241
|
+
url: 'https://typescriptlang.org',
|
|
242
|
+
description: 'Statically typed JavaScript superset',
|
|
243
|
+
version: '5',
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
frameworks: [
|
|
247
|
+
{
|
|
248
|
+
name: 'Next.js',
|
|
249
|
+
url: 'https://nextjs.org',
|
|
250
|
+
description: 'Full-stack React framework with App Router, SSR, and API routes',
|
|
251
|
+
version: '15',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'React',
|
|
255
|
+
url: 'https://react.dev',
|
|
256
|
+
description: 'Component-based UI library',
|
|
257
|
+
version: '19',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
databases: [
|
|
261
|
+
{
|
|
262
|
+
name: 'SQLite',
|
|
263
|
+
url: 'https://sqlite.org',
|
|
264
|
+
description: 'Embedded relational database — zero config, upgradeable to hosted DB',
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
libraries: [
|
|
268
|
+
{
|
|
269
|
+
name: 'Prisma',
|
|
270
|
+
url: 'https://prisma.io',
|
|
271
|
+
description: 'Type-safe database ORM and query builder',
|
|
272
|
+
version: '7',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'Tailwind CSS',
|
|
276
|
+
url: 'https://tailwindcss.com',
|
|
277
|
+
description: 'Utility-first CSS framework',
|
|
278
|
+
version: '4',
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
case 'chrome-extension-react':
|
|
283
|
+
return {
|
|
284
|
+
languages: [
|
|
285
|
+
{
|
|
286
|
+
name: 'TypeScript',
|
|
287
|
+
url: 'https://typescriptlang.org',
|
|
288
|
+
description: 'Statically typed JavaScript superset',
|
|
289
|
+
version: '5',
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
frameworks: [
|
|
293
|
+
{
|
|
294
|
+
name: 'React',
|
|
295
|
+
url: 'https://react.dev',
|
|
296
|
+
description: 'Component-based UI library for the popup and options pages',
|
|
297
|
+
version: '19',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'Vite',
|
|
301
|
+
url: 'https://vite.dev',
|
|
302
|
+
description: 'Fast build tool and dev server',
|
|
303
|
+
version: '6',
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
libraries: [
|
|
307
|
+
{
|
|
308
|
+
name: 'Tailwind CSS',
|
|
309
|
+
url: 'https://tailwindcss.com',
|
|
310
|
+
description: 'Utility-first CSS framework',
|
|
311
|
+
version: '4',
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
infrastructure: [
|
|
315
|
+
{
|
|
316
|
+
name: 'Chrome Manifest V3',
|
|
317
|
+
url: 'https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3',
|
|
318
|
+
description: 'Extension platform with service workers, declarative APIs, and chrome.storage',
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
case 'expo-react-native':
|
|
323
|
+
return {
|
|
324
|
+
languages: [
|
|
325
|
+
{
|
|
326
|
+
name: 'TypeScript',
|
|
327
|
+
url: 'https://typescriptlang.org',
|
|
328
|
+
description: 'Statically typed JavaScript superset',
|
|
329
|
+
version: '5',
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
frameworks: [
|
|
333
|
+
{
|
|
334
|
+
name: 'Expo',
|
|
335
|
+
url: 'https://expo.dev',
|
|
336
|
+
description: 'React Native development platform with managed workflow and OTA updates',
|
|
337
|
+
version: 'SDK 54',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'React Native',
|
|
341
|
+
url: 'https://reactnative.dev',
|
|
342
|
+
description: 'Cross-platform mobile UI framework',
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'Expo Router',
|
|
346
|
+
url: 'https://docs.expo.dev/router/introduction/',
|
|
347
|
+
description: 'File-based navigation for React Native apps',
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
libraries: [
|
|
351
|
+
{
|
|
352
|
+
name: 'NativeWind',
|
|
353
|
+
url: 'https://www.nativewind.dev',
|
|
354
|
+
description: 'Tailwind CSS for React Native',
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'AsyncStorage',
|
|
358
|
+
url: 'https://react-native-async-storage.github.io/async-storage/',
|
|
359
|
+
description: 'Persistent key-value storage for React Native',
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
};
|
|
363
|
+
default:
|
|
364
|
+
return {};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
180
367
|
/**
|
|
181
368
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
182
369
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -200,14 +387,76 @@ function checkbox(text) {
|
|
|
200
387
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
201
388
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
202
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Print a checklist item for the handoff summary.
|
|
392
|
+
*/
|
|
393
|
+
function checkboxHandoff() {
|
|
394
|
+
checkbox('Update the handoff summary in `.codeyam/config.json` for the next session/AI: `codeyam editor handoff \'{"summary":"..."}\'`');
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Print the handoff context from a previous session or AI provider.
|
|
398
|
+
*/
|
|
399
|
+
function printHandoffContext(root) {
|
|
400
|
+
try {
|
|
401
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
402
|
+
if (!fs.existsSync(configPath))
|
|
403
|
+
return;
|
|
404
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
405
|
+
const handoff = config.handoff;
|
|
406
|
+
if (!handoff || !handoff.summary)
|
|
407
|
+
return;
|
|
408
|
+
console.log();
|
|
409
|
+
console.log(chalk.bold.magenta(`━━━ HANDOFF FROM ${String(handoff.lastProvider || 'unknown').toUpperCase()} (Step ${handoff.lastStep || 'unknown'}) ━━━`));
|
|
410
|
+
console.log(chalk.magenta(handoff.summary));
|
|
411
|
+
if (handoff.lastUpdated) {
|
|
412
|
+
console.log(chalk.dim(` Updated: ${handoff.lastUpdated}`));
|
|
413
|
+
}
|
|
414
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
415
|
+
console.log();
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// Non-fatal
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
423
|
+
* Only prints creation instructions if the file doesn't exist yet.
|
|
424
|
+
* If it exists, reminds Claude to update it if data models changed.
|
|
425
|
+
*/
|
|
426
|
+
function printDataStructureInstructions() {
|
|
427
|
+
const root = getProjectRoot();
|
|
428
|
+
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
429
|
+
const exists = fs.existsSync(dsPath);
|
|
430
|
+
if (!exists) {
|
|
431
|
+
console.log(chalk.bold('Create the data structure config:'));
|
|
432
|
+
checkbox('Create `.codeyam/data-structure.json` describing all data sources');
|
|
433
|
+
console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
|
|
434
|
+
console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
|
|
435
|
+
console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
|
|
436
|
+
console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
|
|
437
|
+
console.log();
|
|
438
|
+
console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
|
|
439
|
+
console.log(chalk.dim(' "mock-api" for mocked external API responses'));
|
|
440
|
+
console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
|
|
441
|
+
console.log(chalk.dim(' Do NOT include types only used as component props.'));
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
|
|
445
|
+
console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
|
|
446
|
+
}
|
|
447
|
+
console.log();
|
|
448
|
+
}
|
|
203
449
|
/**
|
|
204
450
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
205
451
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
206
452
|
*/
|
|
207
453
|
function printAppScenarioInstructions(pageName, route) {
|
|
454
|
+
const root = process.cwd();
|
|
455
|
+
const hasSeedAdapter = !!detectSeedAdapter(root);
|
|
208
456
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
209
|
-
console.log(chalk.dim(' Review existing scenarios —
|
|
210
|
-
console.log(chalk.dim('
|
|
457
|
+
console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
|
|
458
|
+
console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
|
|
459
|
+
console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
|
|
211
460
|
console.log();
|
|
212
461
|
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
213
462
|
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
@@ -224,21 +473,42 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
224
473
|
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
225
474
|
}
|
|
226
475
|
console.log();
|
|
476
|
+
checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
|
|
477
|
+
console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
|
|
478
|
+
console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
|
|
479
|
+
console.log();
|
|
227
480
|
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
228
481
|
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
229
482
|
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
230
483
|
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
231
484
|
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
232
485
|
console.log();
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
486
|
+
if (hasSeedAdapter) {
|
|
487
|
+
checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data — without it the page will be empty!'));
|
|
488
|
+
console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
|
|
489
|
+
console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
|
|
490
|
+
console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
|
|
491
|
+
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
const appCtx = getTechStackContext(root);
|
|
495
|
+
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
496
|
+
if (appCtx.isExpo) {
|
|
497
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
498
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
499
|
+
}
|
|
500
|
+
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
501
|
+
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
502
|
+
}
|
|
503
|
+
console.log();
|
|
504
|
+
console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
|
|
505
|
+
console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
|
|
506
|
+
console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
|
|
507
|
+
console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
508
|
+
console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
|
|
509
|
+
console.log();
|
|
240
510
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
241
|
-
checkbox('After
|
|
511
|
+
checkbox('After registration, check the response for `clientErrors`');
|
|
242
512
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
243
513
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
244
514
|
}
|
|
@@ -274,6 +544,12 @@ function printExtractionPlanInstructions() {
|
|
|
274
544
|
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
275
545
|
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
276
546
|
console.log();
|
|
547
|
+
console.log(chalk.bold.red('THE DECOMPOSITION RULE: All code should do one thing.'));
|
|
548
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces to form a coordinated whole.'));
|
|
549
|
+
console.log(chalk.yellow(' Every opportunity to extract code into a sensible function, helper, or sub-component MUST be taken.'));
|
|
550
|
+
console.log(chalk.yellow(' Then extract AGAIN from the extracted code — keep going until each piece does one clearly defined thing.'));
|
|
551
|
+
console.log(chalk.yellow(' This applies to ALL code: backend routes, business logic, frontend components, utilities — everything.'));
|
|
552
|
+
console.log();
|
|
277
553
|
console.log(chalk.bold('Checklist:'));
|
|
278
554
|
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
279
555
|
checkbox('Read EVERY file used by this page/feature');
|
|
@@ -284,6 +560,12 @@ function printExtractionPlanInstructions() {
|
|
|
284
560
|
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
285
561
|
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
286
562
|
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
563
|
+
console.log(chalk.yellow(' Inline logic — backend AND frontend (MUST be extracted as named functions):'));
|
|
564
|
+
console.log(chalk.yellow(' conditionals/ternaries, computed/derived values, data transformations,'));
|
|
565
|
+
console.log(chalk.yellow(' string formatting/interpolation, array filtering/sorting/mapping callbacks,'));
|
|
566
|
+
console.log(chalk.yellow(' default value logic, null/undefined guards, date/number formatting,'));
|
|
567
|
+
console.log(chalk.yellow(' permission/role checks, status derivation (e.g. isOverdue(task)),'));
|
|
568
|
+
console.log(chalk.yellow(' request/response shaping, error message construction, config lookups'));
|
|
287
569
|
console.log();
|
|
288
570
|
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
289
571
|
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
@@ -294,17 +576,34 @@ function printExtractionPlanInstructions() {
|
|
|
294
576
|
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
295
577
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
296
578
|
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
579
|
+
console.log(chalk.yellow(' — How you will test it (key inputs/outputs and edge cases)'));
|
|
580
|
+
console.log(chalk.yellow(' — What logic inside it can be further extracted as a separate testable function'));
|
|
297
581
|
console.log();
|
|
298
|
-
console.log(chalk.
|
|
582
|
+
console.log(chalk.bold.red('BEFORE FINALIZING THE PLAN:'));
|
|
583
|
+
console.log(chalk.yellow(' Re-read every file ONE MORE TIME. For each piece of code, ask:'));
|
|
584
|
+
console.log(chalk.yellow(' 1. Does this do more than one thing? → Split it into pieces that each do one thing.'));
|
|
585
|
+
console.log(chalk.yellow(' 2. Is there logic here that could be its own function with clear inputs/outputs? → Extract it.'));
|
|
586
|
+
console.log(chalk.yellow(' 3. Can the extracted code itself be broken down further? → Keep extracting.'));
|
|
587
|
+
console.log(chalk.yellow(' If your plan has fewer functions than components, you probably missed extractable logic.'));
|
|
588
|
+
console.log();
|
|
589
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
299
590
|
}
|
|
300
591
|
/**
|
|
301
592
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
302
593
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
303
594
|
*/
|
|
304
|
-
function printComponentCaptureInstructions() {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
595
|
+
function printComponentCaptureInstructions(root) {
|
|
596
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
597
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
598
|
+
checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
599
|
+
if (isExpo) {
|
|
600
|
+
console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
|
|
601
|
+
console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
|
|
605
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
606
|
+
}
|
|
308
607
|
checkbox('For each visual component:');
|
|
309
608
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
310
609
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -315,23 +614,37 @@ function printComponentCaptureInstructions() {
|
|
|
315
614
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
316
615
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
317
616
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
318
|
-
|
|
319
|
-
|
|
617
|
+
if (isExpo) {
|
|
618
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
619
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
623
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
624
|
+
}
|
|
320
625
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
321
626
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
627
|
+
if (isExpo) {
|
|
628
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
629
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
633
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
634
|
+
}
|
|
635
|
+
console.log(chalk.dim(isExpo
|
|
636
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
637
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
638
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
326
639
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
327
640
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
328
641
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
329
642
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
330
643
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
331
644
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
332
|
-
console.log(chalk.dim(` "url":"/
|
|
645
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
333
646
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
334
|
-
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/
|
|
647
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
335
648
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
336
649
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
337
650
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -339,6 +652,10 @@ function printComponentCaptureInstructions() {
|
|
|
339
652
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
340
653
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
341
654
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
655
|
+
console.log();
|
|
656
|
+
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
657
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
658
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
342
659
|
}
|
|
343
660
|
/**
|
|
344
661
|
* Print a section header.
|
|
@@ -352,13 +669,13 @@ function stepHeader(step, title, feature) {
|
|
|
352
669
|
console.log();
|
|
353
670
|
}
|
|
354
671
|
/**
|
|
355
|
-
* Print a colored progress tracker showing all
|
|
672
|
+
* Print a colored progress tracker showing all 18 steps.
|
|
356
673
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
357
674
|
*/
|
|
358
675
|
function printProgressTracker(current) {
|
|
359
676
|
console.log();
|
|
360
677
|
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
361
|
-
for (let i = 1; i <=
|
|
678
|
+
for (let i = 1; i <= 18; i++) {
|
|
362
679
|
const label = STEP_LABELS[i];
|
|
363
680
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
364
681
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -379,9 +696,33 @@ function printProgressTracker(current) {
|
|
|
379
696
|
*
|
|
380
697
|
* Options:
|
|
381
698
|
* - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
|
|
699
|
+
* - feature: string → feature name for task directive
|
|
382
700
|
*/
|
|
383
701
|
function stopGate(current, opts) {
|
|
384
|
-
|
|
702
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
703
|
+
const currentLabel = STEP_LABELS[current] || `Step ${current}`;
|
|
704
|
+
const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
|
|
705
|
+
console.log();
|
|
706
|
+
// ━━━ TASK ━━━ — deterministic task lifecycle directive
|
|
707
|
+
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
708
|
+
if (current >= 2) {
|
|
709
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
710
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
711
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
712
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
713
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
714
|
+
console.log();
|
|
715
|
+
let taskTitle;
|
|
716
|
+
if (current < totalSteps) {
|
|
717
|
+
taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
taskTitle =
|
|
721
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
722
|
+
}
|
|
723
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
724
|
+
console.log();
|
|
725
|
+
}
|
|
385
726
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
386
727
|
console.log();
|
|
387
728
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -390,20 +731,12 @@ function stopGate(current, opts) {
|
|
|
390
731
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
391
732
|
}
|
|
392
733
|
console.log();
|
|
393
|
-
|
|
394
|
-
printProgressTracker(current);
|
|
395
|
-
console.log();
|
|
396
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
397
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
398
|
-
console.log();
|
|
399
|
-
if (current < 16) {
|
|
734
|
+
if (current < totalSteps) {
|
|
400
735
|
console.log(chalk.green('When done, run: ') +
|
|
401
736
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
402
737
|
}
|
|
403
738
|
else {
|
|
404
|
-
console.log(chalk.green('Feature complete!
|
|
405
|
-
chalk.bold('codeyam editor 1') +
|
|
406
|
-
chalk.green(' to start the next feature'));
|
|
739
|
+
console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
|
|
407
740
|
}
|
|
408
741
|
console.log();
|
|
409
742
|
}
|
|
@@ -418,34 +751,36 @@ function printResumptionHeader(step) {
|
|
|
418
751
|
'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
419
752
|
],
|
|
420
753
|
2: ['Check if project files already exist:\n `ls package.json src/`'],
|
|
421
|
-
3: ['
|
|
422
|
-
4: [
|
|
754
|
+
3: ['Re-check is safe — just re-run the step'],
|
|
755
|
+
4: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
756
|
+
5: ['This is a confirmation step — just re-present to user'],
|
|
757
|
+
6: [
|
|
423
758
|
'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
424
759
|
],
|
|
425
|
-
|
|
760
|
+
7: [
|
|
426
761
|
'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
|
|
427
762
|
],
|
|
428
|
-
|
|
763
|
+
8: [
|
|
429
764
|
'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
|
|
430
765
|
],
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
766
|
+
9: ['Check if isolation routes and registered scenarios already exist'],
|
|
767
|
+
10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
|
|
768
|
+
11: [
|
|
434
769
|
'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
|
|
435
770
|
],
|
|
436
|
-
|
|
437
|
-
|
|
771
|
+
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
772
|
+
13: [
|
|
438
773
|
'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
|
|
439
774
|
'If an entry exists, use PATCH to update it — do NOT create a new one',
|
|
440
775
|
],
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
776
|
+
14: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
777
|
+
15: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
778
|
+
16: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
779
|
+
17: [
|
|
445
780
|
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
446
781
|
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
447
782
|
],
|
|
448
|
-
|
|
783
|
+
18: [
|
|
449
784
|
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
450
785
|
],
|
|
451
786
|
};
|
|
@@ -552,7 +887,7 @@ function parseDebugTargets(target) {
|
|
|
552
887
|
}
|
|
553
888
|
if (entry.startsWith('step-')) {
|
|
554
889
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
555
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
890
|
+
if (!isNaN(num) && num >= 1 && num <= 18) {
|
|
556
891
|
valid.add(`step-${num}`);
|
|
557
892
|
continue;
|
|
558
893
|
}
|
|
@@ -638,11 +973,17 @@ function printSetup(root) {
|
|
|
638
973
|
console.log();
|
|
639
974
|
// ── Design System ────────────────────────────────────────────────
|
|
640
975
|
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
641
|
-
console.log(chalk.dim(' Ask: "
|
|
976
|
+
console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
|
|
642
977
|
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
643
|
-
console.log(
|
|
978
|
+
console.log();
|
|
979
|
+
for (const ds of DESIGN_SYSTEMS) {
|
|
980
|
+
console.log(chalk.yellow(` Option label: "${ds.name}"`) +
|
|
981
|
+
chalk.dim(` — ${ds.description}`));
|
|
982
|
+
console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
|
|
983
|
+
}
|
|
984
|
+
console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
|
|
644
985
|
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
645
|
-
console.log(chalk.yellow(' Option
|
|
986
|
+
console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
|
|
646
987
|
console.log(chalk.dim(' → Skip'));
|
|
647
988
|
console.log();
|
|
648
989
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -663,8 +1004,8 @@ function printSetup(root) {
|
|
|
663
1004
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
664
1005
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
665
1006
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
666
|
-
console.log(chalk.dim('
|
|
667
|
-
console.log(chalk.dim('
|
|
1007
|
+
console.log(chalk.dim(' If only ONE stack matches, confirm it directly (e.g. "Expo + React Native is the only stack for mobile apps — using that.").'));
|
|
1008
|
+
console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
|
|
668
1009
|
console.log();
|
|
669
1010
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
670
1011
|
for (const stack of TECH_STACKS) {
|
|
@@ -697,7 +1038,7 @@ function printSetup(root) {
|
|
|
697
1038
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
698
1039
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
699
1040
|
console.log();
|
|
700
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
1041
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
|
|
701
1042
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
702
1043
|
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
703
1044
|
console.log();
|
|
@@ -769,37 +1110,39 @@ function printCycleOverview(root, state) {
|
|
|
769
1110
|
console.log();
|
|
770
1111
|
console.log(chalk.green('Continue with: ') +
|
|
771
1112
|
chalk.bold(`codeyam editor ${state.step}`));
|
|
772
|
-
console.log(chalk.dim('Or run ')
|
|
773
|
-
chalk.bold('codeyam editor 1') +
|
|
774
|
-
chalk.dim(' to start a new feature'));
|
|
1113
|
+
console.log(chalk.dim('Or ask the user what to build next, then run `codeyam editor 1` yourself (never expose this command to the user)'));
|
|
775
1114
|
console.log();
|
|
776
1115
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
777
1116
|
chalk.bold('codeyam editor change'));
|
|
778
1117
|
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
779
1118
|
}
|
|
780
1119
|
else {
|
|
781
|
-
console.log('Each feature follows
|
|
1120
|
+
console.log('Each feature follows 18 steps. You MUST run each command in order:');
|
|
782
1121
|
console.log();
|
|
783
|
-
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')}
|
|
784
|
-
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('
|
|
785
|
-
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('
|
|
786
|
-
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('
|
|
787
|
-
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('
|
|
788
|
-
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('
|
|
789
|
-
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('
|
|
790
|
-
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('
|
|
791
|
-
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('
|
|
792
|
-
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('
|
|
793
|
-
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('
|
|
794
|
-
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('
|
|
795
|
-
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('
|
|
796
|
-
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('
|
|
797
|
-
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('
|
|
798
|
-
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('
|
|
1122
|
+
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
1123
|
+
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} — Set up project and dependencies`);
|
|
1124
|
+
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
1125
|
+
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} — Verify prototype works correctly`);
|
|
1126
|
+
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} — Confirm prototype with user`);
|
|
1127
|
+
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
|
|
1128
|
+
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} — TDD extraction of functions + components`);
|
|
1129
|
+
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} — Record functions in glossary`);
|
|
1130
|
+
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} — Analyze and verify components`);
|
|
1131
|
+
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
|
|
1132
|
+
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
|
|
1133
|
+
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
|
|
1134
|
+
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
1135
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
1136
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
1137
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
1138
|
+
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
1139
|
+
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
799
1140
|
console.log();
|
|
800
|
-
console.log(chalk.green('Start now: ') +
|
|
1141
|
+
console.log(chalk.green('Start now: ') +
|
|
1142
|
+
'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
|
|
801
1143
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
802
1144
|
}
|
|
1145
|
+
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
803
1146
|
console.log();
|
|
804
1147
|
}
|
|
805
1148
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
@@ -854,6 +1197,15 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
854
1197
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
855
1198
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
856
1199
|
console.log();
|
|
1200
|
+
checkbox('Detect and set the project tech stack:');
|
|
1201
|
+
console.log(chalk.dim(' Scan package.json, framework configs (.env, .env.example, tsconfig.json, next.config.*, prisma/schema.prisma, etc.)'));
|
|
1202
|
+
console.log(chalk.dim(' to detect languages, frameworks, databases, services, libraries, and infrastructure.'));
|
|
1203
|
+
console.log(chalk.dim(' Each item MUST have name, url, and description. version and envKeys are optional.'));
|
|
1204
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
1205
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
1206
|
+
console.log(chalk.dim(' -d \'{"techStack":{"languages":[{"name":"TypeScript","url":"https://typescriptlang.org","description":"Statically typed JavaScript superset","version":"5"}],"frameworks":[{"name":"Next.js","url":"https://nextjs.org","description":"Full-stack React framework with SSR and API routes","version":"15"}],"databases":[{"name":"SQLite","url":"https://sqlite.org","description":"Embedded relational database"}],"services":[{"name":"Stripe","url":"https://stripe.com","description":"Payment processing and billing","envKeys":["STRIPE_SECRET_KEY"]}],"libraries":[{"name":"Prisma","url":"https://prisma.io","description":"Type-safe database ORM","version":"7"}],"infrastructure":[]}}\''));
|
|
1207
|
+
console.log(chalk.dim(' Replace the example values with what you detect in this project. Omit empty categories.'));
|
|
1208
|
+
console.log();
|
|
857
1209
|
const designSystem = readDesignSystem(root);
|
|
858
1210
|
if (designSystem) {
|
|
859
1211
|
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
@@ -867,6 +1219,8 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
867
1219
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
868
1220
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
869
1221
|
console.log();
|
|
1222
|
+
checkboxHandoff();
|
|
1223
|
+
console.log();
|
|
870
1224
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
871
1225
|
console.log();
|
|
872
1226
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
@@ -875,18 +1229,12 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
875
1229
|
console.log();
|
|
876
1230
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
877
1231
|
console.log();
|
|
878
|
-
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
879
|
-
printProgressTracker(1);
|
|
880
|
-
console.log();
|
|
881
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
882
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
883
|
-
console.log();
|
|
884
1232
|
console.log(chalk.green('When done, run: ') +
|
|
885
1233
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
886
1234
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
887
1235
|
console.log();
|
|
888
1236
|
}
|
|
889
|
-
// ─── Step 2:
|
|
1237
|
+
// ─── Step 2: Prepare ──────────────────────────────────────────────────
|
|
890
1238
|
function printStep2(root, feature) {
|
|
891
1239
|
const port = getServerPort();
|
|
892
1240
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
@@ -903,41 +1251,87 @@ function printStep2(root, feature) {
|
|
|
903
1251
|
appFormats: prevState?.appFormats,
|
|
904
1252
|
techStackId: prevState?.techStackId,
|
|
905
1253
|
});
|
|
906
|
-
logEvent(root, 'step', { step: 2, label: '
|
|
907
|
-
stepHeader(2, '
|
|
1254
|
+
logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
|
|
1255
|
+
stepHeader(2, 'Prepare', feature);
|
|
908
1256
|
if (isResuming) {
|
|
909
1257
|
printResumptionHeader(2);
|
|
910
1258
|
}
|
|
911
|
-
console.log('
|
|
1259
|
+
console.log('Get the project ready to build.');
|
|
912
1260
|
console.log();
|
|
1261
|
+
const ctx = getTechStackContext(root);
|
|
913
1262
|
// If no project exists yet, include scaffolding instructions first
|
|
914
1263
|
if (!projectExists) {
|
|
915
1264
|
console.log(chalk.bold('Scaffold the project:'));
|
|
916
1265
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
917
|
-
|
|
1266
|
+
if (ctx.isExpo) {
|
|
1267
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1268
|
+
}
|
|
1269
|
+
else if (ctx.isChromeExt) {
|
|
1270
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1274
|
+
}
|
|
918
1275
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
919
1276
|
console.log();
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1277
|
+
if (ctx.isExpo) {
|
|
1278
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1279
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1280
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1281
|
+
console.log();
|
|
1282
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1283
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1284
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1285
|
+
console.log();
|
|
1286
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1287
|
+
console.log();
|
|
1288
|
+
checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
|
|
1289
|
+
console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
|
|
1290
|
+
console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
|
|
1291
|
+
port +
|
|
1292
|
+
'/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
|
|
1293
|
+
console.log();
|
|
1294
|
+
checkbox('Generate an app icon using `sharp`');
|
|
1295
|
+
console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
|
|
1296
|
+
console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
|
|
1297
|
+
console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
|
|
1298
|
+
console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
|
|
1299
|
+
console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
|
|
1300
|
+
console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
|
|
1301
|
+
console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
|
|
1302
|
+
console.log();
|
|
1303
|
+
}
|
|
1304
|
+
else if (ctx.isChromeExt) {
|
|
1305
|
+
// Chrome Extension: use chrome.storage
|
|
1306
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1307
|
+
console.log();
|
|
1308
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1309
|
+
console.log();
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
// Next.js: Prisma + database
|
|
1313
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1314
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1315
|
+
console.log();
|
|
1316
|
+
checkbox('Push schema and seed the database');
|
|
1317
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1318
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1319
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1320
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1321
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1322
|
+
console.log();
|
|
1323
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1324
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1325
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1326
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1327
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1328
|
+
console.log();
|
|
1329
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1330
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1331
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1332
|
+
console.log();
|
|
1333
|
+
}
|
|
1334
|
+
printDataStructureInstructions();
|
|
941
1335
|
}
|
|
942
1336
|
else {
|
|
943
1337
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -952,18 +1346,70 @@ function printStep2(root, feature) {
|
|
|
952
1346
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
953
1347
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
954
1348
|
console.log();
|
|
1349
|
+
printDataStructureInstructions();
|
|
1350
|
+
}
|
|
1351
|
+
console.log();
|
|
1352
|
+
checkboxHandoff();
|
|
1353
|
+
stopGate(2);
|
|
1354
|
+
}
|
|
1355
|
+
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
1356
|
+
function printStep3(root, feature) {
|
|
1357
|
+
const port = getServerPort();
|
|
1358
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1359
|
+
const projectExists = hasProject(root);
|
|
1360
|
+
const prevState = readState(root);
|
|
1361
|
+
const isResuming = prevState?.step === 3;
|
|
1362
|
+
const now = new Date().toISOString();
|
|
1363
|
+
writeState(root, {
|
|
1364
|
+
feature,
|
|
1365
|
+
step: 3,
|
|
1366
|
+
label: STEP_LABELS[3],
|
|
1367
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1368
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1369
|
+
appFormats: prevState?.appFormats,
|
|
1370
|
+
techStackId: prevState?.techStackId,
|
|
1371
|
+
});
|
|
1372
|
+
logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
|
|
1373
|
+
stepHeader(3, 'Prototype', feature);
|
|
1374
|
+
if (isResuming) {
|
|
1375
|
+
printResumptionHeader(3);
|
|
955
1376
|
}
|
|
1377
|
+
const ctx = getTechStackContext(root);
|
|
1378
|
+
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1379
|
+
console.log();
|
|
956
1380
|
console.log(chalk.bold('Checklist:'));
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1381
|
+
if (ctx.isExpo) {
|
|
1382
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1383
|
+
if (!projectExists) {
|
|
1384
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1385
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1386
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1387
|
+
console.log();
|
|
1388
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1389
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1390
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1391
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
else {
|
|
1395
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1396
|
+
if (!projectExists) {
|
|
1397
|
+
checkbox('Seed the database with demo data');
|
|
1398
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1399
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1400
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1401
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1402
|
+
console.log();
|
|
1403
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1404
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1405
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1406
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1407
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1408
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1409
|
+
}
|
|
964
1410
|
}
|
|
965
1411
|
checkbox('Verify the dev server shows the changes');
|
|
966
|
-
checkbox(
|
|
1412
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
967
1413
|
// Responsive design guidance when building a mobile-responsive web app
|
|
968
1414
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
969
1415
|
console.log();
|
|
@@ -980,16 +1426,33 @@ function printStep2(root, feature) {
|
|
|
980
1426
|
console.log();
|
|
981
1427
|
console.log(designSystem);
|
|
982
1428
|
console.log();
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1429
|
+
if (ctx.isExpo) {
|
|
1430
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1431
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1432
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1433
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1434
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1435
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1436
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1437
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1438
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1439
|
+
checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
|
|
1440
|
+
console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
|
|
1441
|
+
console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
|
|
1442
|
+
console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1446
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1447
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1448
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1449
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1450
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1451
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1452
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1453
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1454
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1455
|
+
}
|
|
993
1456
|
}
|
|
994
1457
|
console.log();
|
|
995
1458
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1000,16 +1463,42 @@ function printStep2(root, feature) {
|
|
|
1000
1463
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1001
1464
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1002
1465
|
printDimensionGuidance(dim, dimNames);
|
|
1466
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1467
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1468
|
+
console.log();
|
|
1469
|
+
checkboxHandoff();
|
|
1470
|
+
stopGate(3);
|
|
1471
|
+
}
|
|
1472
|
+
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
1473
|
+
function printStep4(root, feature) {
|
|
1474
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1475
|
+
const prevState = readState(root);
|
|
1476
|
+
const isResuming = prevState?.step === 4;
|
|
1477
|
+
const now = new Date().toISOString();
|
|
1478
|
+
writeState(root, {
|
|
1479
|
+
feature,
|
|
1480
|
+
step: 4,
|
|
1481
|
+
label: STEP_LABELS[4],
|
|
1482
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1483
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1484
|
+
appFormats: prevState?.appFormats,
|
|
1485
|
+
techStackId: prevState?.techStackId,
|
|
1486
|
+
});
|
|
1487
|
+
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1488
|
+
stepHeader(4, 'Verify Prototype', feature);
|
|
1489
|
+
if (isResuming) {
|
|
1490
|
+
printResumptionHeader(4);
|
|
1491
|
+
}
|
|
1492
|
+
console.log('Verify everything works before presenting the prototype.');
|
|
1003
1493
|
console.log();
|
|
1004
1494
|
console.log(chalk.bold('Verify the dev server:'));
|
|
1005
|
-
console.log(chalk.dim(
|
|
1006
|
-
console.log(chalk.dim(
|
|
1007
|
-
console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
|
|
1495
|
+
console.log(chalk.dim(' # Verify pages and API routes load:'));
|
|
1496
|
+
console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
|
|
1008
1497
|
console.log();
|
|
1009
1498
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1010
1499
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
1011
|
-
checkbox('Verify
|
|
1012
|
-
|
|
1500
|
+
checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1501
|
+
console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
|
|
1013
1502
|
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1014
1503
|
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
1015
1504
|
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
@@ -1020,6 +1509,14 @@ function printStep2(root, feature) {
|
|
|
1020
1509
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1021
1510
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1022
1511
|
console.log();
|
|
1512
|
+
const ctx4 = getTechStackContext(root);
|
|
1513
|
+
if (ctx4.isExpo) {
|
|
1514
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1515
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1516
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1517
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1518
|
+
console.log();
|
|
1519
|
+
}
|
|
1023
1520
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1024
1521
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1025
1522
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1027,32 +1524,36 @@ function printStep2(root, feature) {
|
|
|
1027
1524
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1028
1525
|
console.log();
|
|
1029
1526
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1030
|
-
|
|
1527
|
+
console.log();
|
|
1528
|
+
checkboxHandoff();
|
|
1529
|
+
stopGate(4);
|
|
1031
1530
|
}
|
|
1032
|
-
// ─── Step
|
|
1033
|
-
function
|
|
1531
|
+
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
1532
|
+
function printStep5(root, feature) {
|
|
1034
1533
|
const port = getServerPort();
|
|
1035
1534
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1036
1535
|
const prevState = readState(root);
|
|
1037
|
-
const isResuming = prevState?.step ===
|
|
1536
|
+
const isResuming = prevState?.step === 5;
|
|
1038
1537
|
const now = new Date().toISOString();
|
|
1039
1538
|
writeState(root, {
|
|
1040
1539
|
feature,
|
|
1041
|
-
step:
|
|
1042
|
-
label: STEP_LABELS[
|
|
1540
|
+
step: 5,
|
|
1541
|
+
label: STEP_LABELS[5],
|
|
1043
1542
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1044
1543
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1544
|
+
appFormats: prevState?.appFormats,
|
|
1545
|
+
techStackId: prevState?.techStackId,
|
|
1045
1546
|
});
|
|
1046
|
-
logEvent(root, 'step', { step:
|
|
1047
|
-
stepHeader(
|
|
1547
|
+
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1548
|
+
stepHeader(5, 'Confirm', feature);
|
|
1048
1549
|
if (isResuming) {
|
|
1049
|
-
printResumptionHeader(
|
|
1550
|
+
printResumptionHeader(5);
|
|
1050
1551
|
}
|
|
1051
1552
|
console.log('Summarize what was built and get user confirmation.');
|
|
1052
1553
|
console.log();
|
|
1053
1554
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
1054
1555
|
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
1055
|
-
checkbox('Verify
|
|
1556
|
+
checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1056
1557
|
console.log();
|
|
1057
1558
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
1058
1559
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
@@ -1063,12 +1564,19 @@ function printStep3(root, feature) {
|
|
|
1063
1564
|
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
1064
1565
|
checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
|
|
1065
1566
|
console.log();
|
|
1567
|
+
console.log(chalk.bold('Verify the captured user prompt:'));
|
|
1568
|
+
checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
|
|
1569
|
+
checkbox('If the file is missing or does not match what the user actually asked for, write the correct prompt text to `.codeyam/editor-user-prompt.txt`');
|
|
1570
|
+
console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
|
|
1571
|
+
console.log();
|
|
1066
1572
|
console.log(chalk.bold('Then present to the user:'));
|
|
1067
1573
|
checkbox('Summarize what was built (routes, components, data)');
|
|
1068
1574
|
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
1069
1575
|
printDimensionGuidance(dim, dimNames);
|
|
1070
1576
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1071
1577
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
1578
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1579
|
+
console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
|
|
1072
1580
|
console.log();
|
|
1073
1581
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1074
1582
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1076,80 +1584,115 @@ function printStep3(root, feature) {
|
|
|
1076
1584
|
checkbox('Ask the user: "Does everything work as expected?"');
|
|
1077
1585
|
console.log();
|
|
1078
1586
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1079
|
-
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step
|
|
1587
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
|
|
1080
1588
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
1081
|
-
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor
|
|
1589
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1082
1590
|
console.log();
|
|
1083
1591
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1084
|
-
|
|
1592
|
+
console.log();
|
|
1593
|
+
checkboxHandoff();
|
|
1594
|
+
stopGate(5, { confirm: true });
|
|
1085
1595
|
}
|
|
1086
|
-
// ─── Step
|
|
1087
|
-
function
|
|
1596
|
+
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
1597
|
+
function printStep6(root, feature) {
|
|
1088
1598
|
const prevState = readState(root);
|
|
1089
|
-
const isResuming = prevState?.step ===
|
|
1599
|
+
const isResuming = prevState?.step === 6;
|
|
1090
1600
|
const now = new Date().toISOString();
|
|
1091
1601
|
writeState(root, {
|
|
1092
1602
|
feature,
|
|
1093
|
-
step:
|
|
1094
|
-
label: STEP_LABELS[
|
|
1603
|
+
step: 6,
|
|
1604
|
+
label: STEP_LABELS[6],
|
|
1095
1605
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1096
1606
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1607
|
+
appFormats: prevState?.appFormats,
|
|
1608
|
+
techStackId: prevState?.techStackId,
|
|
1097
1609
|
});
|
|
1098
|
-
logEvent(root, 'step', { step:
|
|
1099
|
-
stepHeader(
|
|
1610
|
+
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1611
|
+
stepHeader(6, 'Deconstruct', feature);
|
|
1100
1612
|
if (isResuming) {
|
|
1101
|
-
printResumptionHeader(
|
|
1613
|
+
printResumptionHeader(6);
|
|
1102
1614
|
}
|
|
1103
1615
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
1104
|
-
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step
|
|
1616
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1105
1617
|
console.log();
|
|
1106
1618
|
printExtractionPlanInstructions();
|
|
1107
|
-
|
|
1619
|
+
console.log();
|
|
1620
|
+
checkboxHandoff();
|
|
1621
|
+
stopGate(6);
|
|
1108
1622
|
}
|
|
1109
|
-
// ─── Step
|
|
1110
|
-
function
|
|
1623
|
+
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
1624
|
+
function printStep7(root, feature) {
|
|
1111
1625
|
const port = getServerPort();
|
|
1112
1626
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1113
1627
|
const prevState = readState(root);
|
|
1114
|
-
const isResuming = prevState?.step ===
|
|
1628
|
+
const isResuming = prevState?.step === 7;
|
|
1115
1629
|
const now = new Date().toISOString();
|
|
1116
1630
|
writeState(root, {
|
|
1117
1631
|
feature,
|
|
1118
|
-
step:
|
|
1119
|
-
label: STEP_LABELS[
|
|
1632
|
+
step: 7,
|
|
1633
|
+
label: STEP_LABELS[7],
|
|
1120
1634
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1121
1635
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1636
|
+
appFormats: prevState?.appFormats,
|
|
1637
|
+
techStackId: prevState?.techStackId,
|
|
1122
1638
|
});
|
|
1123
|
-
logEvent(root, 'step', { step:
|
|
1124
|
-
stepHeader(
|
|
1639
|
+
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1640
|
+
stepHeader(7, 'Extract', feature);
|
|
1125
1641
|
if (isResuming) {
|
|
1126
|
-
printResumptionHeader(
|
|
1642
|
+
printResumptionHeader(7);
|
|
1127
1643
|
}
|
|
1128
|
-
|
|
1644
|
+
const ctx = getTechStackContext(root);
|
|
1645
|
+
console.log('Execute your extraction plan from step 6.');
|
|
1129
1646
|
console.log();
|
|
1130
1647
|
console.log(chalk.bold('Components:'));
|
|
1131
1648
|
checkbox('Extract each component from your plan into its own file');
|
|
1132
1649
|
checkbox('Page/route files must contain ZERO direct JSX — only imported components');
|
|
1133
1650
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
1134
|
-
console.log(chalk.dim(' No tests needed — visual verification happens in step
|
|
1651
|
+
console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
|
|
1135
1652
|
console.log();
|
|
1136
1653
|
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
1137
1654
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1138
1655
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1139
1656
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1657
|
+
console.log(chalk.dim(' EVERY conditional branch in the function MUST have a test that exercises it'));
|
|
1658
|
+
console.log(chalk.dim(' EVERY return path MUST be tested — if a function can return 3 different things, write 3+ tests'));
|
|
1659
|
+
console.log(chalk.dim(' Boundary values: 0, 1, -1, empty string, empty array, undefined, null'));
|
|
1140
1660
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1141
|
-
|
|
1142
|
-
|
|
1661
|
+
console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
|
|
1662
|
+
if (ctx.isExpo) {
|
|
1663
|
+
checkbox('Place test files next to source but OUTSIDE `app/` (Expo Router treats all files in app/ as routes): `lib/storage.ts` → `lib/storage.test.ts`, `app/hooks/useCounter.ts` → `__tests__/hooks/useCounter.test.ts`');
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1667
|
+
}
|
|
1668
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1143
1669
|
console.log();
|
|
1144
1670
|
console.log(chalk.bold('Recursive pass:'));
|
|
1145
1671
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1146
1672
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1147
|
-
console.log(chalk.yellow(
|
|
1673
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1148
1674
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1149
1675
|
console.log();
|
|
1676
|
+
console.log(chalk.bold('Decomposition pass (backend AND frontend):'));
|
|
1677
|
+
checkbox('Re-read EVERY file you created or modified. For each, extract a function/helper/sub-component for:');
|
|
1678
|
+
console.log(chalk.yellow(' — Conditionals (e.g. `isOverdue ? "red" : "green"` → `getStatusColor(date)`)'));
|
|
1679
|
+
console.log(chalk.yellow(' — Computed values (e.g. `items.filter(...).length` → `countActiveItems(items)`)'));
|
|
1680
|
+
console.log(chalk.yellow(' — Formatting (e.g. `${price.toFixed(2)}` → `formatPrice(price)`)'));
|
|
1681
|
+
console.log(chalk.yellow(' — Data transforms, validation, request/response shaping'));
|
|
1682
|
+
console.log(chalk.yellow(' — Anything doing more than one thing → split until each piece does one clearly defined thing'));
|
|
1683
|
+
checkbox('Then look at what you just extracted — can IT be broken down further? Keep going.');
|
|
1684
|
+
checkbox('Write tests for every extracted function (same TDD: failing tests FIRST)');
|
|
1685
|
+
console.log();
|
|
1686
|
+
console.log(chalk.bold.red('TEST QUALITY SELF-CHECK (before proceeding):'));
|
|
1687
|
+
checkbox('For EACH test file: count the test cases. Fewer than 3 tests for any function is a red flag.');
|
|
1688
|
+
checkbox('For EACH tested function: read the function, count the conditional branches. Every branch MUST have a test.');
|
|
1689
|
+
checkbox('For EACH tested function: verify you test at least one error/edge case, not just happy paths.');
|
|
1690
|
+
console.log(chalk.yellow(' If a function has an if/else, switch, or ternary — each path needs its own test case.'));
|
|
1691
|
+
console.log(chalk.yellow(' If a function handles null/undefined/empty — test those inputs explicitly.'));
|
|
1692
|
+
console.log();
|
|
1150
1693
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1151
1694
|
checkbox('Run all tests and verify they pass');
|
|
1152
|
-
checkbox(
|
|
1695
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1153
1696
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1154
1697
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1155
1698
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1157,93 +1700,117 @@ function printStep5(root, feature) {
|
|
|
1157
1700
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1158
1701
|
console.log();
|
|
1159
1702
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1160
|
-
|
|
1703
|
+
console.log();
|
|
1704
|
+
checkboxHandoff();
|
|
1705
|
+
stopGate(7);
|
|
1161
1706
|
}
|
|
1162
|
-
// ─── Step
|
|
1163
|
-
function
|
|
1707
|
+
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
1708
|
+
function printStep8(root, feature) {
|
|
1164
1709
|
const prevState = readState(root);
|
|
1165
|
-
const isResuming = prevState?.step ===
|
|
1710
|
+
const isResuming = prevState?.step === 8;
|
|
1166
1711
|
const now = new Date().toISOString();
|
|
1167
1712
|
writeState(root, {
|
|
1168
1713
|
feature,
|
|
1169
|
-
step:
|
|
1170
|
-
label: STEP_LABELS[
|
|
1714
|
+
step: 8,
|
|
1715
|
+
label: STEP_LABELS[8],
|
|
1171
1716
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1172
1717
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1718
|
+
appFormats: prevState?.appFormats,
|
|
1719
|
+
techStackId: prevState?.techStackId,
|
|
1173
1720
|
});
|
|
1174
|
-
logEvent(root, 'step', { step:
|
|
1175
|
-
stepHeader(
|
|
1721
|
+
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1722
|
+
stepHeader(8, 'Glossary', feature);
|
|
1176
1723
|
if (isResuming) {
|
|
1177
|
-
printResumptionHeader(
|
|
1724
|
+
printResumptionHeader(8);
|
|
1178
1725
|
}
|
|
1179
1726
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
1180
1727
|
console.log();
|
|
1181
1728
|
printGlossaryInstructions(feature);
|
|
1182
1729
|
console.log();
|
|
1183
1730
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
1184
|
-
stopGate(
|
|
1731
|
+
stopGate(8);
|
|
1185
1732
|
}
|
|
1186
|
-
// ─── Step
|
|
1187
|
-
function
|
|
1733
|
+
// ─── Step 9: Analyze ──────────────────────────────────────────────────
|
|
1734
|
+
function printStep9(root, feature) {
|
|
1188
1735
|
const port = getServerPort();
|
|
1189
1736
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1190
1737
|
const prevState = readState(root);
|
|
1191
|
-
const isResuming = prevState?.step ===
|
|
1738
|
+
const isResuming = prevState?.step === 9;
|
|
1192
1739
|
const now = new Date().toISOString();
|
|
1193
1740
|
writeState(root, {
|
|
1194
1741
|
feature,
|
|
1195
|
-
step:
|
|
1196
|
-
label: STEP_LABELS[
|
|
1742
|
+
step: 9,
|
|
1743
|
+
label: STEP_LABELS[9],
|
|
1197
1744
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1198
1745
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1746
|
+
appFormats: prevState?.appFormats,
|
|
1747
|
+
techStackId: prevState?.techStackId,
|
|
1199
1748
|
});
|
|
1200
|
-
logEvent(root, 'step', { step:
|
|
1201
|
-
stepHeader(
|
|
1749
|
+
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1750
|
+
stepHeader(9, 'Analyze and Verify', feature);
|
|
1202
1751
|
if (isResuming) {
|
|
1203
|
-
printResumptionHeader(
|
|
1752
|
+
printResumptionHeader(9);
|
|
1204
1753
|
}
|
|
1205
1754
|
console.log('Verify visual components (via isolation routes) and library functions (via tests).');
|
|
1206
1755
|
console.log();
|
|
1207
1756
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
1208
|
-
checkbox('List all files with new/modified visual components from step
|
|
1757
|
+
checkbox('List all files with new/modified visual components from step 7');
|
|
1209
1758
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
1210
1759
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1211
1760
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1212
1761
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1213
|
-
|
|
1762
|
+
const ctx9 = getTechStackContext(root);
|
|
1763
|
+
printComponentCaptureInstructions(root);
|
|
1214
1764
|
console.log();
|
|
1215
1765
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1216
|
-
checkbox('Run ALL test files created in step
|
|
1217
|
-
console.log(chalk.dim(
|
|
1766
|
+
checkbox('Run ALL test files created in step 7');
|
|
1767
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1218
1768
|
checkbox('Verify every test passes');
|
|
1219
1769
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1220
1770
|
console.log();
|
|
1221
1771
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1222
1772
|
console.log();
|
|
1773
|
+
console.log(chalk.bold.red('DECOMPOSITION & COVERAGE REVIEW (before running audit):'));
|
|
1774
|
+
checkbox('Re-read every file. Extract any logic that could be its own function/helper/sub-component.');
|
|
1775
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces.'));
|
|
1776
|
+
console.log(chalk.yellow(' Keep extracting until each piece does one clearly defined thing.'));
|
|
1777
|
+
console.log(chalk.yellow(' For each extraction: write failing test first, then extract, then make test pass.'));
|
|
1778
|
+
checkbox('Open each test file and verify: ≥3 test cases per function, happy path + edge case + error case');
|
|
1779
|
+
checkbox('If any function has fewer tests than conditional branches, add the missing tests NOW');
|
|
1780
|
+
checkbox('Update `.codeyam/glossary.json` with any new functions before running audit');
|
|
1781
|
+
console.log();
|
|
1223
1782
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1224
|
-
console.log(chalk.red.bold(' The audit is a HARD GATE — step
|
|
1225
|
-
console.log(chalk.dim('
|
|
1783
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1784
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1785
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1786
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1787
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1788
|
+
console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
|
|
1789
|
+
console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
|
|
1790
|
+
console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
|
|
1226
1791
|
console.log();
|
|
1227
|
-
stopGate(
|
|
1792
|
+
stopGate(9);
|
|
1228
1793
|
}
|
|
1229
|
-
// ─── Step
|
|
1230
|
-
function
|
|
1794
|
+
// ─── Step 10: App Scenarios ────────────────────────────────────────────
|
|
1795
|
+
function printStep10(root, feature) {
|
|
1231
1796
|
const port = getServerPort();
|
|
1232
1797
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1233
1798
|
const prevState = readState(root);
|
|
1234
|
-
const isResuming = prevState?.step ===
|
|
1799
|
+
const isResuming = prevState?.step === 10;
|
|
1235
1800
|
const now = new Date().toISOString();
|
|
1236
1801
|
writeState(root, {
|
|
1237
1802
|
feature,
|
|
1238
|
-
step:
|
|
1239
|
-
label: STEP_LABELS[
|
|
1803
|
+
step: 10,
|
|
1804
|
+
label: STEP_LABELS[10],
|
|
1240
1805
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1241
1806
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1807
|
+
appFormats: prevState?.appFormats,
|
|
1808
|
+
techStackId: prevState?.techStackId,
|
|
1242
1809
|
});
|
|
1243
|
-
logEvent(root, 'step', { step:
|
|
1244
|
-
stepHeader(
|
|
1810
|
+
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1811
|
+
stepHeader(10, 'App Scenarios', feature);
|
|
1245
1812
|
if (isResuming) {
|
|
1246
|
-
printResumptionHeader(
|
|
1813
|
+
printResumptionHeader(10);
|
|
1247
1814
|
}
|
|
1248
1815
|
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1249
1816
|
console.log();
|
|
@@ -1257,6 +1824,7 @@ function printStep8(root, feature) {
|
|
|
1257
1824
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1258
1825
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1259
1826
|
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1827
|
+
console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
|
|
1260
1828
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1261
1829
|
console.log();
|
|
1262
1830
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1265,13 +1833,19 @@ function printStep8(root, feature) {
|
|
|
1265
1833
|
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1266
1834
|
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
1267
1835
|
console.log();
|
|
1836
|
+
console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
|
|
1837
|
+
console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
|
|
1838
|
+
console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
|
|
1839
|
+
console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
|
|
1840
|
+
console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
|
|
1841
|
+
console.log();
|
|
1268
1842
|
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1269
1843
|
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1270
1844
|
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1271
1845
|
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1272
1846
|
printAppScenarioInstructions();
|
|
1273
1847
|
console.log();
|
|
1274
|
-
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step
|
|
1848
|
+
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
|
|
1275
1849
|
console.log();
|
|
1276
1850
|
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1277
1851
|
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
@@ -1282,43 +1856,45 @@ function printStep8(root, feature) {
|
|
|
1282
1856
|
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1283
1857
|
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1284
1858
|
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1285
|
-
stopGate(
|
|
1859
|
+
stopGate(10);
|
|
1286
1860
|
}
|
|
1287
|
-
// ─── Step
|
|
1288
|
-
function
|
|
1861
|
+
// ─── Step 11: User Scenarios ───────────────────────────────────────────
|
|
1862
|
+
function printStep11(root, feature) {
|
|
1289
1863
|
const port = getServerPort();
|
|
1290
1864
|
const prevState = readState(root);
|
|
1291
|
-
const isResuming = prevState?.step ===
|
|
1865
|
+
const isResuming = prevState?.step === 11;
|
|
1292
1866
|
const now = new Date().toISOString();
|
|
1293
1867
|
writeState(root, {
|
|
1294
1868
|
feature,
|
|
1295
|
-
step:
|
|
1296
|
-
label: STEP_LABELS[
|
|
1869
|
+
step: 11,
|
|
1870
|
+
label: STEP_LABELS[11],
|
|
1297
1871
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1298
1872
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1873
|
+
appFormats: prevState?.appFormats,
|
|
1874
|
+
techStackId: prevState?.techStackId,
|
|
1299
1875
|
});
|
|
1300
|
-
logEvent(root, 'step', { step:
|
|
1301
|
-
stepHeader(
|
|
1876
|
+
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1877
|
+
stepHeader(11, 'User Scenarios', feature);
|
|
1302
1878
|
if (isResuming) {
|
|
1303
|
-
printResumptionHeader(
|
|
1879
|
+
printResumptionHeader(11);
|
|
1304
1880
|
}
|
|
1305
|
-
console.log('Create per-persona variations of existing scenarios. Skip to step
|
|
1881
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
|
|
1306
1882
|
console.log();
|
|
1307
1883
|
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1308
|
-
console.log(chalk.dim(' Skip this step and proceed to step
|
|
1884
|
+
console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
|
|
1309
1885
|
console.log();
|
|
1310
1886
|
console.log(chalk.bold('If the app has users/auth:'));
|
|
1311
1887
|
console.log();
|
|
1312
1888
|
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1313
1889
|
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1314
|
-
console.log(chalk.dim(' a variation of an existing app scenario from step
|
|
1890
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
|
|
1315
1891
|
console.log();
|
|
1316
1892
|
console.log(chalk.bold('Checklist:'));
|
|
1317
1893
|
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1318
1894
|
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1319
1895
|
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1320
1896
|
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1321
|
-
console.log(chalk.dim(' Step
|
|
1897
|
+
console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1322
1898
|
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1323
1899
|
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1324
1900
|
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
@@ -1327,26 +1903,28 @@ function printStep9(root, feature) {
|
|
|
1327
1903
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1328
1904
|
console.log();
|
|
1329
1905
|
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1330
|
-
stopGate(
|
|
1906
|
+
stopGate(11);
|
|
1331
1907
|
}
|
|
1332
|
-
// ─── Step
|
|
1333
|
-
function
|
|
1908
|
+
// ─── Step 12: Verify ──────────────────────────────────────────────────
|
|
1909
|
+
function printStep12(root, feature) {
|
|
1334
1910
|
const port = getServerPort();
|
|
1335
1911
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1336
1912
|
const prevState = readState(root);
|
|
1337
|
-
const isResuming = prevState?.step ===
|
|
1913
|
+
const isResuming = prevState?.step === 12;
|
|
1338
1914
|
const now = new Date().toISOString();
|
|
1339
1915
|
writeState(root, {
|
|
1340
1916
|
feature,
|
|
1341
|
-
step:
|
|
1342
|
-
label: STEP_LABELS[
|
|
1917
|
+
step: 12,
|
|
1918
|
+
label: STEP_LABELS[12],
|
|
1343
1919
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1344
1920
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1921
|
+
appFormats: prevState?.appFormats,
|
|
1922
|
+
techStackId: prevState?.techStackId,
|
|
1345
1923
|
});
|
|
1346
|
-
logEvent(root, 'step', { step:
|
|
1347
|
-
stepHeader(
|
|
1924
|
+
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1925
|
+
stepHeader(12, 'Verify', feature);
|
|
1348
1926
|
if (isResuming) {
|
|
1349
|
-
printResumptionHeader(
|
|
1927
|
+
printResumptionHeader(12);
|
|
1350
1928
|
}
|
|
1351
1929
|
console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
|
|
1352
1930
|
console.log();
|
|
@@ -1369,63 +1947,67 @@ function printStep10(root, feature) {
|
|
|
1369
1947
|
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1370
1948
|
console.log();
|
|
1371
1949
|
console.log(chalk.bold('Library functions — test check:'));
|
|
1372
|
-
checkbox('Re-run all test files from step
|
|
1950
|
+
checkbox('Re-run all test files from step 7 to confirm they still pass');
|
|
1373
1951
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
1374
1952
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1375
1953
|
console.log();
|
|
1376
1954
|
console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
|
|
1377
|
-
stopGate(
|
|
1955
|
+
stopGate(12);
|
|
1378
1956
|
}
|
|
1379
|
-
// ─── Step
|
|
1380
|
-
function
|
|
1957
|
+
// ─── Step 13: Journal ─────────────────────────────────────────────────
|
|
1958
|
+
function printStep13(root, feature) {
|
|
1381
1959
|
const port = getServerPort();
|
|
1382
1960
|
const prevState = readState(root);
|
|
1383
|
-
const isResuming = prevState?.step ===
|
|
1961
|
+
const isResuming = prevState?.step === 13;
|
|
1384
1962
|
const now = new Date().toISOString();
|
|
1385
1963
|
writeState(root, {
|
|
1386
1964
|
feature,
|
|
1387
|
-
step:
|
|
1388
|
-
label: STEP_LABELS[
|
|
1965
|
+
step: 13,
|
|
1966
|
+
label: STEP_LABELS[13],
|
|
1389
1967
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1390
1968
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1969
|
+
appFormats: prevState?.appFormats,
|
|
1970
|
+
techStackId: prevState?.techStackId,
|
|
1391
1971
|
});
|
|
1392
|
-
logEvent(root, 'step', { step:
|
|
1393
|
-
stepHeader(
|
|
1972
|
+
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1973
|
+
stepHeader(13, 'Journal', feature);
|
|
1394
1974
|
if (isResuming) {
|
|
1395
|
-
printResumptionHeader(
|
|
1975
|
+
printResumptionHeader(13);
|
|
1396
1976
|
}
|
|
1397
1977
|
console.log('Create or update the journal entry for this feature.');
|
|
1398
1978
|
console.log();
|
|
1399
1979
|
console.log(chalk.bold('Checklist:'));
|
|
1400
1980
|
checkbox('Write a concise description of what was built (2-3 sentences)');
|
|
1401
|
-
checkbox(`First time at step
|
|
1981
|
+
checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
|
|
1402
1982
|
console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
1403
1983
|
console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
|
|
1404
|
-
checkbox(`Returning to step
|
|
1984
|
+
checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
|
|
1405
1985
|
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1406
1986
|
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1407
1987
|
console.log();
|
|
1408
|
-
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step
|
|
1409
|
-
stopGate(
|
|
1988
|
+
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
|
|
1989
|
+
stopGate(13);
|
|
1410
1990
|
}
|
|
1411
|
-
// ─── Step
|
|
1412
|
-
function
|
|
1991
|
+
// ─── Step 14: Review ──────────────────────────────────────────────────
|
|
1992
|
+
function printStep14(root, feature) {
|
|
1413
1993
|
const port = getServerPort();
|
|
1414
1994
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1415
1995
|
const prevState = readState(root);
|
|
1416
|
-
const isResuming = prevState?.step ===
|
|
1996
|
+
const isResuming = prevState?.step === 14;
|
|
1417
1997
|
const now = new Date().toISOString();
|
|
1418
1998
|
writeState(root, {
|
|
1419
1999
|
feature,
|
|
1420
|
-
step:
|
|
1421
|
-
label: STEP_LABELS[
|
|
2000
|
+
step: 14,
|
|
2001
|
+
label: STEP_LABELS[14],
|
|
1422
2002
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1423
2003
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2004
|
+
appFormats: prevState?.appFormats,
|
|
2005
|
+
techStackId: prevState?.techStackId,
|
|
1424
2006
|
});
|
|
1425
|
-
logEvent(root, 'step', { step:
|
|
1426
|
-
stepHeader(
|
|
2007
|
+
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
2008
|
+
stepHeader(14, 'Review', feature);
|
|
1427
2009
|
if (isResuming) {
|
|
1428
|
-
printResumptionHeader(
|
|
2010
|
+
printResumptionHeader(14);
|
|
1429
2011
|
}
|
|
1430
2012
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1431
2013
|
console.log();
|
|
@@ -1438,33 +2020,36 @@ function printStep12(root, feature) {
|
|
|
1438
2020
|
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1439
2021
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1440
2022
|
checkbox('Fix or remove any broken images before continuing');
|
|
2023
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1441
2024
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
1442
2025
|
checkbox('Do not proceed until all checks pass');
|
|
1443
|
-
stopGate(
|
|
2026
|
+
stopGate(14);
|
|
1444
2027
|
}
|
|
1445
|
-
// ─── Step
|
|
1446
|
-
function
|
|
2028
|
+
// ─── Step 15: Present ─────────────────────────────────────────────────
|
|
2029
|
+
function printStep15(root, feature) {
|
|
1447
2030
|
const prevState = readState(root);
|
|
1448
|
-
const isResuming = prevState?.step ===
|
|
2031
|
+
const isResuming = prevState?.step === 15;
|
|
1449
2032
|
const now = new Date().toISOString();
|
|
1450
2033
|
writeState(root, {
|
|
1451
2034
|
feature,
|
|
1452
|
-
step:
|
|
1453
|
-
label: STEP_LABELS[
|
|
2035
|
+
step: 15,
|
|
2036
|
+
label: STEP_LABELS[15],
|
|
1454
2037
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1455
2038
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2039
|
+
appFormats: prevState?.appFormats,
|
|
2040
|
+
techStackId: prevState?.techStackId,
|
|
1456
2041
|
});
|
|
1457
|
-
logEvent(root, 'step', { step:
|
|
1458
|
-
stepHeader(
|
|
2042
|
+
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
2043
|
+
stepHeader(15, 'Present', feature);
|
|
1459
2044
|
if (isResuming) {
|
|
1460
|
-
printResumptionHeader(
|
|
2045
|
+
printResumptionHeader(15);
|
|
1461
2046
|
}
|
|
1462
2047
|
console.log('Present the results to the user and get their approval.');
|
|
1463
2048
|
console.log();
|
|
1464
2049
|
console.log(chalk.bold('Checklist:'));
|
|
1465
2050
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1466
2051
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1467
|
-
console.log(chalk.dim(' The
|
|
2052
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1468
2053
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1469
2054
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1470
2055
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1476,7 +2061,7 @@ function printStep13(root, feature) {
|
|
|
1476
2061
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1477
2062
|
console.log();
|
|
1478
2063
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1479
|
-
checkbox('Advance to the commit step: `codeyam editor
|
|
2064
|
+
checkbox('Advance to the commit step: `codeyam editor 16`');
|
|
1480
2065
|
console.log();
|
|
1481
2066
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1482
2067
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1484,7 +2069,7 @@ function printStep13(root, feature) {
|
|
|
1484
2069
|
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1485
2070
|
checkbox('THEN make the requested changes and follow the checklist');
|
|
1486
2071
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1487
|
-
stopGate(
|
|
2072
|
+
stopGate(15, { confirm: true });
|
|
1488
2073
|
}
|
|
1489
2074
|
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1490
2075
|
/**
|
|
@@ -1681,7 +2266,7 @@ function printMigrateStep3(root) {
|
|
|
1681
2266
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1682
2267
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1683
2268
|
console.log();
|
|
1684
|
-
printComponentCaptureInstructions();
|
|
2269
|
+
printComponentCaptureInstructions(root);
|
|
1685
2270
|
console.log();
|
|
1686
2271
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1687
2272
|
}
|
|
@@ -1704,7 +2289,7 @@ function printMigrateStep4(root) {
|
|
|
1704
2289
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1705
2290
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1706
2291
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1707
|
-
console.log(chalk.dim(' The
|
|
2292
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1708
2293
|
console.log();
|
|
1709
2294
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1710
2295
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1816,7 +2401,7 @@ function printMigrateStep8(root) {
|
|
|
1816
2401
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1817
2402
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1818
2403
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1819
|
-
checkbox('Find all scenarios that use the real page route (not /
|
|
2404
|
+
checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
|
|
1820
2405
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1821
2406
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1822
2407
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1824,10 +2409,10 @@ function printMigrateStep8(root) {
|
|
|
1824
2409
|
console.log();
|
|
1825
2410
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1826
2411
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1827
|
-
printComponentCaptureInstructions();
|
|
2412
|
+
printComponentCaptureInstructions(root);
|
|
1828
2413
|
console.log();
|
|
1829
2414
|
console.log(chalk.bold('Verify:'));
|
|
1830
|
-
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
2415
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
1831
2416
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1832
2417
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1833
2418
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -1871,7 +2456,7 @@ function printMigrateStep10(root) {
|
|
|
1871
2456
|
console.log();
|
|
1872
2457
|
console.log(chalk.bold('Checklist:'));
|
|
1873
2458
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1874
|
-
console.log(chalk.dim(' The
|
|
2459
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1875
2460
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1876
2461
|
console.log();
|
|
1877
2462
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2091,47 +2676,52 @@ function handleMigrateCommand(root, subArg) {
|
|
|
2091
2676
|
};
|
|
2092
2677
|
stepFns[step](root);
|
|
2093
2678
|
}
|
|
2094
|
-
// ─── Step
|
|
2095
|
-
function
|
|
2679
|
+
// ─── Step 16: Commit ─────────────────────────────────────────────────
|
|
2680
|
+
function printStep16(root, feature) {
|
|
2096
2681
|
const prevState = readState(root);
|
|
2097
|
-
const isResuming = prevState?.step ===
|
|
2682
|
+
const isResuming = prevState?.step === 16;
|
|
2098
2683
|
const now = new Date().toISOString();
|
|
2099
2684
|
writeState(root, {
|
|
2100
2685
|
feature,
|
|
2101
|
-
step:
|
|
2102
|
-
label: STEP_LABELS[
|
|
2686
|
+
step: 16,
|
|
2687
|
+
label: STEP_LABELS[16],
|
|
2103
2688
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2104
2689
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2690
|
+
appFormats: prevState?.appFormats,
|
|
2691
|
+
techStackId: prevState?.techStackId,
|
|
2105
2692
|
});
|
|
2106
|
-
logEvent(root, 'step', { step:
|
|
2107
|
-
stepHeader(
|
|
2693
|
+
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2694
|
+
stepHeader(16, 'Commit', feature);
|
|
2108
2695
|
if (isResuming) {
|
|
2109
|
-
printResumptionHeader(
|
|
2696
|
+
printResumptionHeader(16);
|
|
2110
2697
|
}
|
|
2111
2698
|
console.log('Commit all changes for this feature.');
|
|
2112
2699
|
console.log();
|
|
2113
2700
|
console.log(chalk.bold('Checklist:'));
|
|
2701
|
+
checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
|
|
2114
2702
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
2115
2703
|
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
2116
2704
|
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
2117
|
-
stopGate(
|
|
2705
|
+
stopGate(16);
|
|
2118
2706
|
}
|
|
2119
|
-
// ─── Step
|
|
2120
|
-
function
|
|
2707
|
+
// ─── Step 17: Finalize ───────────────────────────────────────────────
|
|
2708
|
+
function printStep17(root, feature) {
|
|
2121
2709
|
const prevState = readState(root);
|
|
2122
|
-
const isResuming = prevState?.step ===
|
|
2710
|
+
const isResuming = prevState?.step === 17;
|
|
2123
2711
|
const now = new Date().toISOString();
|
|
2124
2712
|
writeState(root, {
|
|
2125
2713
|
feature,
|
|
2126
|
-
step:
|
|
2127
|
-
label: STEP_LABELS[
|
|
2714
|
+
step: 17,
|
|
2715
|
+
label: STEP_LABELS[17],
|
|
2128
2716
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2129
2717
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2718
|
+
appFormats: prevState?.appFormats,
|
|
2719
|
+
techStackId: prevState?.techStackId,
|
|
2130
2720
|
});
|
|
2131
|
-
logEvent(root, 'step', { step:
|
|
2132
|
-
stepHeader(
|
|
2721
|
+
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2722
|
+
stepHeader(17, 'Finalize', feature);
|
|
2133
2723
|
if (isResuming) {
|
|
2134
|
-
printResumptionHeader(
|
|
2724
|
+
printResumptionHeader(17);
|
|
2135
2725
|
}
|
|
2136
2726
|
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
2137
2727
|
console.log();
|
|
@@ -2139,24 +2729,26 @@ function printStep15(root, feature) {
|
|
|
2139
2729
|
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
2140
2730
|
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
2141
2731
|
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
2142
|
-
stopGate(
|
|
2732
|
+
stopGate(17);
|
|
2143
2733
|
}
|
|
2144
|
-
// ─── Step
|
|
2145
|
-
function
|
|
2734
|
+
// ─── Step 18: Push ───────────────────────────────────────────────────
|
|
2735
|
+
function printStep18(root, feature) {
|
|
2146
2736
|
const prevState = readState(root);
|
|
2147
|
-
const isResuming = prevState?.step ===
|
|
2737
|
+
const isResuming = prevState?.step === 18;
|
|
2148
2738
|
const now = new Date().toISOString();
|
|
2149
2739
|
writeState(root, {
|
|
2150
2740
|
feature,
|
|
2151
|
-
step:
|
|
2152
|
-
label: STEP_LABELS[
|
|
2741
|
+
step: 18,
|
|
2742
|
+
label: STEP_LABELS[18],
|
|
2153
2743
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2154
2744
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2745
|
+
appFormats: prevState?.appFormats,
|
|
2746
|
+
techStackId: prevState?.techStackId,
|
|
2155
2747
|
});
|
|
2156
|
-
logEvent(root, 'step', { step:
|
|
2157
|
-
stepHeader(
|
|
2748
|
+
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2749
|
+
stepHeader(18, 'Push', feature);
|
|
2158
2750
|
if (isResuming) {
|
|
2159
|
-
printResumptionHeader(
|
|
2751
|
+
printResumptionHeader(18);
|
|
2160
2752
|
}
|
|
2161
2753
|
console.log('Push the commit to the remote repository.');
|
|
2162
2754
|
console.log();
|
|
@@ -2167,12 +2759,15 @@ function printStep16(root, feature) {
|
|
|
2167
2759
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2168
2760
|
console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
|
|
2169
2761
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2170
|
-
|
|
2762
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2171
2763
|
console.log();
|
|
2172
|
-
|
|
2173
|
-
console.log(chalk.
|
|
2174
|
-
console.log(chalk.
|
|
2175
|
-
|
|
2764
|
+
const port = getServerPort();
|
|
2765
|
+
console.log(chalk.bold.yellow('IMPORTANT: After the push succeeds (or the user skips it), the feature is DONE.'));
|
|
2766
|
+
console.log(chalk.yellow(` Signal the UI by running: curl -s -X POST http://localhost:${port}/api/editor-feature-complete`));
|
|
2767
|
+
console.log(chalk.yellow(' Then STOP and wait. Do NOT ask the user what to build next — the UI handles the transition.'));
|
|
2768
|
+
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2769
|
+
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2770
|
+
stopGate(18, { confirm: true });
|
|
2176
2771
|
}
|
|
2177
2772
|
// ─── Command definition ───────────────────────────────────────────────
|
|
2178
2773
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
@@ -2192,7 +2787,7 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2192
2787
|
return;
|
|
2193
2788
|
}
|
|
2194
2789
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
2195
|
-
console.error(chalk.dim(' Run codeyam editor
|
|
2790
|
+
console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
|
|
2196
2791
|
process.exit(1);
|
|
2197
2792
|
}
|
|
2198
2793
|
let glossaryEntries;
|
|
@@ -2201,10 +2796,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2201
2796
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2202
2797
|
}
|
|
2203
2798
|
catch {
|
|
2799
|
+
if (options.silent)
|
|
2800
|
+
return;
|
|
2204
2801
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2205
2802
|
process.exit(1);
|
|
2206
2803
|
}
|
|
2207
2804
|
if (glossaryEntries.length === 0) {
|
|
2805
|
+
if (options.silent)
|
|
2806
|
+
return;
|
|
2208
2807
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2209
2808
|
process.exit(1);
|
|
2210
2809
|
}
|
|
@@ -2246,25 +2845,137 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2246
2845
|
// Non-fatal — scenario file paths just won't be included
|
|
2247
2846
|
}
|
|
2248
2847
|
const progress = new ProgressReporter();
|
|
2249
|
-
//
|
|
2848
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2849
|
+
// ~120 files when only 2 are incomplete.
|
|
2850
|
+
let targetFilePaths = filePaths;
|
|
2851
|
+
try {
|
|
2852
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2853
|
+
const db = getDatabase();
|
|
2854
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2855
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2856
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2857
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2858
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2859
|
+
if (!options.silent) {
|
|
2860
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2861
|
+
}
|
|
2862
|
+
targetFilePaths = incomplete;
|
|
2863
|
+
}
|
|
2864
|
+
else if (incomplete.length === 0) {
|
|
2865
|
+
if (!options.silent) {
|
|
2866
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2867
|
+
}
|
|
2868
|
+
// Still need to run the import graph + backfill below
|
|
2869
|
+
targetFilePaths = [];
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
catch {
|
|
2873
|
+
// Non-fatal — fall back to analyzing all files
|
|
2874
|
+
}
|
|
2875
|
+
// If specific file paths were requested, scope analysis to only those files.
|
|
2876
|
+
// The full filePaths set is still used for the import graph below.
|
|
2877
|
+
if (options.filePaths && options.filePaths.length > 0) {
|
|
2878
|
+
const requested = new Set(options.filePaths);
|
|
2879
|
+
targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
|
|
2880
|
+
if (targetFilePaths.length === 0 && !options.silent) {
|
|
2881
|
+
console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2250
2885
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2251
2886
|
// The analyzer will discover and create them from file paths.
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2887
|
+
if (targetFilePaths.length > 0) {
|
|
2888
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2889
|
+
try {
|
|
2890
|
+
await runAnalysisForEntities({
|
|
2891
|
+
projectRoot: root,
|
|
2892
|
+
filePaths: targetFilePaths,
|
|
2893
|
+
progress,
|
|
2894
|
+
onlyDataStructure: true,
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
catch (err) {
|
|
2898
|
+
progress.fail('Analysis failed');
|
|
2899
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2900
|
+
if (options.silent) {
|
|
2901
|
+
// Internal caller — don't kill the process, let the caller handle it
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2904
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2905
|
+
process.exit(1);
|
|
2906
|
+
}
|
|
2907
|
+
progress.succeed('Analysis complete');
|
|
2260
2908
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2909
|
+
// Surface analysis errors if any occurred
|
|
2910
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2911
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2912
|
+
if (!options.silent) {
|
|
2913
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2914
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2915
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2916
|
+
}
|
|
2917
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2918
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2919
|
+
// with the files we tried to analyze.
|
|
2920
|
+
try {
|
|
2921
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2922
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2923
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2924
|
+
// Parse error messages to identify failed file paths.
|
|
2925
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2926
|
+
// or more generic "CodeYam Error: <message>"
|
|
2927
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2928
|
+
const failedEntityNames = new Set();
|
|
2929
|
+
let match;
|
|
2930
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2931
|
+
const name = match[1] || match[2];
|
|
2932
|
+
if (name)
|
|
2933
|
+
failedEntityNames.add(name);
|
|
2934
|
+
}
|
|
2935
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2936
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2937
|
+
let glossary = [];
|
|
2938
|
+
try {
|
|
2939
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2940
|
+
}
|
|
2941
|
+
catch {
|
|
2942
|
+
// Non-fatal
|
|
2943
|
+
}
|
|
2944
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2945
|
+
const now = new Date().toISOString();
|
|
2946
|
+
const updatedFailures = { ...existingFailures };
|
|
2947
|
+
if (failedEntityNames.size > 0) {
|
|
2948
|
+
for (const name of failedEntityNames) {
|
|
2949
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2950
|
+
if (entry) {
|
|
2951
|
+
updatedFailures[entry.filePath] = {
|
|
2952
|
+
entityName: name,
|
|
2953
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2954
|
+
failedAt: now,
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
// When we can't parse specific entity names from the error, DON'T mark
|
|
2960
|
+
// all target files as failed. The error may be non-fatal (e.g., a cache
|
|
2961
|
+
// miss logged as "CodeYam Error") and blanket-marking every file as
|
|
2962
|
+
// permanently failed blocks future audits from resolving them.
|
|
2963
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2964
|
+
}
|
|
2965
|
+
catch {
|
|
2966
|
+
// Non-fatal — failure tracking is best-effort
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
else {
|
|
2970
|
+
// No errors — clear any stale failure tracking
|
|
2971
|
+
try {
|
|
2972
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2973
|
+
writeAnalysisFailures(root, {});
|
|
2974
|
+
}
|
|
2975
|
+
catch {
|
|
2976
|
+
// Non-fatal
|
|
2977
|
+
}
|
|
2266
2978
|
}
|
|
2267
|
-
progress.succeed('Analysis complete');
|
|
2268
2979
|
// Load entities WITH metadata from database
|
|
2269
2980
|
progress.start('Loading entity metadata...');
|
|
2270
2981
|
await initializeEnvironment();
|
|
@@ -2447,43 +3158,98 @@ async function handleDelete(scenarioId) {
|
|
|
2447
3158
|
* Creates isolation route directories and the layout guard file.
|
|
2448
3159
|
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2449
3160
|
*/
|
|
3161
|
+
function handleDesignSystem(designSystemId) {
|
|
3162
|
+
const root = process.cwd();
|
|
3163
|
+
const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
|
|
3164
|
+
if (!ds) {
|
|
3165
|
+
console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
|
|
3166
|
+
process.exit(1);
|
|
3167
|
+
}
|
|
3168
|
+
const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
|
|
3169
|
+
if (!fs.existsSync(srcPath)) {
|
|
3170
|
+
console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
|
|
3171
|
+
process.exit(1);
|
|
3172
|
+
}
|
|
3173
|
+
const destDir = path.join(root, '.codeyam');
|
|
3174
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
3175
|
+
const destPath = path.join(destDir, 'design-system.md');
|
|
3176
|
+
fs.copyFileSync(srcPath, destPath);
|
|
3177
|
+
console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
|
|
3178
|
+
}
|
|
2450
3179
|
function handleIsolate(componentNames) {
|
|
2451
3180
|
const root = process.cwd();
|
|
3181
|
+
const ctx = getTechStackContext(root);
|
|
2452
3182
|
if (componentNames.length === 0) {
|
|
2453
3183
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2454
3184
|
process.exit(1);
|
|
2455
3185
|
}
|
|
2456
3186
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2457
|
-
// Create
|
|
2458
|
-
|
|
3187
|
+
// Create the framework-appropriate layout guard if missing.
|
|
3188
|
+
// Clean up wrong-framework layout from a previous CLI version.
|
|
3189
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
3190
|
+
const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
|
|
3191
|
+
if (fs.existsSync(wrongLayoutPath)) {
|
|
3192
|
+
fs.unlinkSync(wrongLayoutPath);
|
|
3193
|
+
}
|
|
2459
3194
|
if (!fs.existsSync(layoutPath)) {
|
|
2460
3195
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
3196
|
+
if (ctx.isExpo) {
|
|
3197
|
+
fs.writeFileSync(layoutPath, [
|
|
3198
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
3199
|
+
'',
|
|
3200
|
+
'export default function CaptureLayout() {',
|
|
3201
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
3202
|
+
' return <Slot />;',
|
|
3203
|
+
'}',
|
|
3204
|
+
'',
|
|
3205
|
+
].join('\n'), 'utf8');
|
|
3206
|
+
}
|
|
3207
|
+
else {
|
|
3208
|
+
fs.writeFileSync(layoutPath, [
|
|
3209
|
+
'import { notFound } from "next/navigation";',
|
|
3210
|
+
'',
|
|
3211
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
3212
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
3213
|
+
' return <>{children}</>;',
|
|
3214
|
+
'}',
|
|
3215
|
+
'',
|
|
3216
|
+
].join('\n'), 'utf8');
|
|
3217
|
+
}
|
|
3218
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
3219
|
+
}
|
|
3220
|
+
// Create isolation route for each component.
|
|
3221
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2473
3222
|
const created = [];
|
|
2474
3223
|
const existed = [];
|
|
2475
3224
|
for (const name of componentNames) {
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
3225
|
+
if (ctx.isExpo) {
|
|
3226
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
3227
|
+
if (fs.existsSync(filePath)) {
|
|
3228
|
+
existed.push(name);
|
|
3229
|
+
}
|
|
3230
|
+
else {
|
|
3231
|
+
created.push(name);
|
|
3232
|
+
}
|
|
2479
3233
|
}
|
|
2480
3234
|
else {
|
|
2481
|
-
|
|
2482
|
-
|
|
3235
|
+
const dir = path.join(isolateDir, name);
|
|
3236
|
+
if (fs.existsSync(dir)) {
|
|
3237
|
+
existed.push(name);
|
|
3238
|
+
}
|
|
3239
|
+
else {
|
|
3240
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3241
|
+
created.push(name);
|
|
3242
|
+
}
|
|
2483
3243
|
}
|
|
2484
3244
|
}
|
|
2485
3245
|
if (created.length > 0) {
|
|
2486
|
-
|
|
3246
|
+
if (ctx.isExpo) {
|
|
3247
|
+
console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
|
|
3248
|
+
console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
|
|
3249
|
+
}
|
|
3250
|
+
else {
|
|
3251
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
3252
|
+
}
|
|
2487
3253
|
}
|
|
2488
3254
|
if (existed.length > 0) {
|
|
2489
3255
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2537,6 +3303,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2537
3303
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2538
3304
|
if (data.sessionsNotified != null)
|
|
2539
3305
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
3306
|
+
// Surface the HTTP health check from the dev server
|
|
3307
|
+
if (data.preview) {
|
|
3308
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
3309
|
+
if (data.preview.error)
|
|
3310
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3311
|
+
}
|
|
2540
3312
|
return parts.join(' ');
|
|
2541
3313
|
}
|
|
2542
3314
|
case 'dev-server': {
|
|
@@ -2586,10 +3358,82 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2586
3358
|
}
|
|
2587
3359
|
return parts.join(' ');
|
|
2588
3360
|
}
|
|
3361
|
+
case 'verify-routes': {
|
|
3362
|
+
const lines = [];
|
|
3363
|
+
lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
|
|
3364
|
+
for (const [route, info] of Object.entries(data.routes || {})) {
|
|
3365
|
+
const r = info;
|
|
3366
|
+
if (r.ok) {
|
|
3367
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
for (const [route, info] of Object.entries(data.apiRoutes || {})) {
|
|
3374
|
+
const r = info;
|
|
3375
|
+
if (r.ok && r.isJSON) {
|
|
3376
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
|
|
3377
|
+
}
|
|
3378
|
+
else if (r.ok) {
|
|
3379
|
+
lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
|
|
3380
|
+
}
|
|
3381
|
+
else {
|
|
3382
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
lines.push('');
|
|
3386
|
+
lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
|
|
3387
|
+
return lines.join('\n');
|
|
3388
|
+
}
|
|
2589
3389
|
default:
|
|
2590
3390
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2591
3391
|
}
|
|
2592
3392
|
}
|
|
3393
|
+
/**
|
|
3394
|
+
* `codeyam editor handoff '{"summary":"..."}'`
|
|
3395
|
+
*
|
|
3396
|
+
* Update the handoff summary in .codeyam/config.json for other AI providers.
|
|
3397
|
+
*/
|
|
3398
|
+
function handleHandoff(jsonArg) {
|
|
3399
|
+
if (!jsonArg) {
|
|
3400
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3401
|
+
console.error(chalk.dim(' Usage: codeyam editor handoff \'{"summary":"Built the X component..."}\''));
|
|
3402
|
+
process.exit(1);
|
|
3403
|
+
}
|
|
3404
|
+
let summary;
|
|
3405
|
+
try {
|
|
3406
|
+
const parsed = JSON.parse(jsonArg);
|
|
3407
|
+
summary = parsed.summary;
|
|
3408
|
+
}
|
|
3409
|
+
catch {
|
|
3410
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
3411
|
+
process.exit(1);
|
|
3412
|
+
}
|
|
3413
|
+
if (!summary) {
|
|
3414
|
+
console.error(chalk.red('Error: "summary" field is required.'));
|
|
3415
|
+
process.exit(1);
|
|
3416
|
+
}
|
|
3417
|
+
const root = getProjectRoot();
|
|
3418
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3419
|
+
try {
|
|
3420
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3421
|
+
const state = readState(root);
|
|
3422
|
+
const provider = config.provider || 'claude';
|
|
3423
|
+
config.handoff = {
|
|
3424
|
+
summary,
|
|
3425
|
+
lastProvider: provider,
|
|
3426
|
+
lastStep: state?.step,
|
|
3427
|
+
lastUpdated: new Date().toISOString(),
|
|
3428
|
+
};
|
|
3429
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
3430
|
+
console.log(chalk.green('✓ Handoff summary updated in .codeyam/config.json'));
|
|
3431
|
+
}
|
|
3432
|
+
catch (err) {
|
|
3433
|
+
console.error(chalk.red(`Error: Could not update config.json: ${err.message}`));
|
|
3434
|
+
process.exit(1);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
2593
3437
|
/**
|
|
2594
3438
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
2595
3439
|
*
|
|
@@ -2614,11 +3458,15 @@ async function handleRegister(jsonArg) {
|
|
|
2614
3458
|
}
|
|
2615
3459
|
// Normalize to array for uniform handling
|
|
2616
3460
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
3461
|
+
const root = getProjectRoot();
|
|
2617
3462
|
const port = getServerPort();
|
|
2618
3463
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2619
3464
|
const isBatch = items.length > 1;
|
|
2620
3465
|
let succeeded = 0;
|
|
2621
3466
|
let failed = 0;
|
|
3467
|
+
let warnings = 0;
|
|
3468
|
+
const issues = [];
|
|
3469
|
+
const screenshotEntries = [];
|
|
2622
3470
|
for (let i = 0; i < items.length; i++) {
|
|
2623
3471
|
const body = items[i];
|
|
2624
3472
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2646,11 +3494,30 @@ async function handleRegister(jsonArg) {
|
|
|
2646
3494
|
else {
|
|
2647
3495
|
parts.push(`errors=0`);
|
|
2648
3496
|
}
|
|
2649
|
-
if (data.
|
|
2650
|
-
|
|
3497
|
+
if (data.visibleText) {
|
|
3498
|
+
const truncated = data.visibleText.length > 100
|
|
3499
|
+
? data.visibleText.substring(0, 97) + '...'
|
|
3500
|
+
: data.visibleText;
|
|
3501
|
+
parts.push(`visibleText="${truncated}"`);
|
|
3502
|
+
}
|
|
3503
|
+
if (data.seedResult) {
|
|
3504
|
+
if (data.seedResult.success) {
|
|
3505
|
+
parts.push(`seed=ok`);
|
|
3506
|
+
}
|
|
3507
|
+
else {
|
|
3508
|
+
parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
2651
3511
|
if (data.captureError)
|
|
2652
3512
|
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
2653
3513
|
console.log(prefix + parts.join(' '));
|
|
3514
|
+
// Warn when application scenario is missing seed data
|
|
3515
|
+
if (data.missingSeedWarning) {
|
|
3516
|
+
console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
|
|
3517
|
+
console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
|
|
3518
|
+
console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
|
|
3519
|
+
console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
|
|
3520
|
+
}
|
|
2654
3521
|
// Surface client errors prominently so they can't be missed
|
|
2655
3522
|
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
2656
3523
|
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
@@ -2658,14 +3525,39 @@ async function handleRegister(jsonArg) {
|
|
|
2658
3525
|
console.log(chalk.red(` → ${err}`));
|
|
2659
3526
|
}
|
|
2660
3527
|
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
3528
|
+
// Provide actionable debugging hints for HTTP/API errors
|
|
3529
|
+
const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
|
|
3530
|
+
const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
|
|
3531
|
+
if (hasHttpError || hasApiResponseError) {
|
|
3532
|
+
console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
|
|
3533
|
+
console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
|
|
3534
|
+
console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
|
|
3535
|
+
}
|
|
2661
3536
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2662
3537
|
}
|
|
3538
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2663
3539
|
if (!res.ok) {
|
|
2664
3540
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2665
3541
|
failed++;
|
|
2666
3542
|
}
|
|
2667
3543
|
else {
|
|
3544
|
+
if (resultIssues.length > 0)
|
|
3545
|
+
warnings++;
|
|
2668
3546
|
succeeded++;
|
|
3547
|
+
// Collect screenshot paths for duplicate detection
|
|
3548
|
+
if (data.screenshotCaptured &&
|
|
3549
|
+
data.scenario?.screenshotPath &&
|
|
3550
|
+
data.scenario?.name) {
|
|
3551
|
+
const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
|
|
3552
|
+
screenshotEntries.push({
|
|
3553
|
+
scenarioName: data.scenario.name,
|
|
3554
|
+
screenshotAbsPath: absPath,
|
|
3555
|
+
});
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
// Collect issues from this registration
|
|
3559
|
+
for (const issue of resultIssues) {
|
|
3560
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
2669
3561
|
}
|
|
2670
3562
|
}
|
|
2671
3563
|
catch (error) {
|
|
@@ -2676,12 +3568,99 @@ async function handleRegister(jsonArg) {
|
|
|
2676
3568
|
failed++;
|
|
2677
3569
|
}
|
|
2678
3570
|
}
|
|
3571
|
+
// Detect duplicate screenshots in the batch
|
|
3572
|
+
if (isBatch && screenshotEntries.length > 1) {
|
|
3573
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
3574
|
+
const hashEntries = screenshotEntries
|
|
3575
|
+
.map((e) => {
|
|
3576
|
+
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
3577
|
+
return hash
|
|
3578
|
+
? {
|
|
3579
|
+
scenarioName: e.scenarioName,
|
|
3580
|
+
screenshotPath: e.screenshotAbsPath,
|
|
3581
|
+
hash,
|
|
3582
|
+
}
|
|
3583
|
+
: null;
|
|
3584
|
+
})
|
|
3585
|
+
.filter((e) => e !== null);
|
|
3586
|
+
const duplicates = findDuplicateScreenshots(hashEntries);
|
|
3587
|
+
if (duplicates.size > 0) {
|
|
3588
|
+
console.log('');
|
|
3589
|
+
console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
|
|
3590
|
+
for (const [, names] of duplicates) {
|
|
3591
|
+
const quoted = names.map((n) => `"${n}"`);
|
|
3592
|
+
console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
|
|
3593
|
+
}
|
|
3594
|
+
console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
|
|
3595
|
+
console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
2679
3598
|
if (isBatch) {
|
|
2680
|
-
console.log(
|
|
3599
|
+
console.log('');
|
|
3600
|
+
if (failed > 0 || warnings > 0) {
|
|
3601
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
3602
|
+
console.log('');
|
|
3603
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
3604
|
+
for (const issue of issues) {
|
|
3605
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
3606
|
+
}
|
|
3607
|
+
console.log('');
|
|
3608
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
3609
|
+
}
|
|
3610
|
+
else {
|
|
3611
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
if (failed > 0 || warnings > 0) {
|
|
3615
|
+
process.exit(1);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
// ─── Glossary-add subcommand ──────────────────────────────────────────
|
|
3619
|
+
/**
|
|
3620
|
+
* `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
|
|
3621
|
+
*
|
|
3622
|
+
* Safely adds/updates entries in .codeyam/glossary.json via the CLI,
|
|
3623
|
+
* avoiding hand-editing that breaks on unicode characters.
|
|
3624
|
+
*/
|
|
3625
|
+
async function handleGlossaryAdd(jsonArg) {
|
|
3626
|
+
if (!jsonArg) {
|
|
3627
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3628
|
+
console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
|
|
3629
|
+
console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
|
|
3630
|
+
process.exit(1);
|
|
2681
3631
|
}
|
|
2682
|
-
|
|
3632
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
3633
|
+
if (parsed.error) {
|
|
3634
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
2683
3635
|
process.exit(1);
|
|
2684
3636
|
}
|
|
3637
|
+
// Normalize to array
|
|
3638
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
3639
|
+
// Read existing glossary
|
|
3640
|
+
const root = getProjectRoot();
|
|
3641
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
3642
|
+
let existing = [];
|
|
3643
|
+
try {
|
|
3644
|
+
const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
3645
|
+
existing = sanitizeGlossaryEntries(raw);
|
|
3646
|
+
}
|
|
3647
|
+
catch {
|
|
3648
|
+
// Glossary doesn't exist yet or can't be parsed — start fresh
|
|
3649
|
+
}
|
|
3650
|
+
// Merge
|
|
3651
|
+
const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
|
|
3652
|
+
const result = mergeGlossaryEntries(existing, items);
|
|
3653
|
+
// Report validation errors
|
|
3654
|
+
for (const err of result.errors) {
|
|
3655
|
+
console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
|
|
3656
|
+
}
|
|
3657
|
+
if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
|
|
3658
|
+
process.exit(1);
|
|
3659
|
+
}
|
|
3660
|
+
// Write back with utf8 encoding to safely handle unicode
|
|
3661
|
+
fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
|
|
3662
|
+
fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
|
|
3663
|
+
console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
|
|
2685
3664
|
}
|
|
2686
3665
|
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
2687
3666
|
/**
|
|
@@ -2804,7 +3783,7 @@ async function handleDependents(entityName) {
|
|
|
2804
3783
|
*
|
|
2805
3784
|
* Prints a condensed post-change checklist that guides Claude through
|
|
2806
3785
|
* re-verifying after user-requested modifications. When called from
|
|
2807
|
-
* step
|
|
3786
|
+
* step 15+, this loops back to step 15 (present). When called from an
|
|
2808
3787
|
* earlier step, it returns to that step so the normal flow continues.
|
|
2809
3788
|
*/
|
|
2810
3789
|
function handleChange(feature) {
|
|
@@ -2822,7 +3801,7 @@ function handleChange(feature) {
|
|
|
2822
3801
|
process.exit(1);
|
|
2823
3802
|
}
|
|
2824
3803
|
}
|
|
2825
|
-
const currentStep = state?.step ??
|
|
3804
|
+
const currentStep = state?.step ?? 15;
|
|
2826
3805
|
const port = getServerPort();
|
|
2827
3806
|
console.log();
|
|
2828
3807
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -2844,6 +3823,8 @@ function handleChange(feature) {
|
|
|
2844
3823
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2845
3824
|
printDimensionGuidance(dim, dimNames);
|
|
2846
3825
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
3826
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
3827
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
2847
3828
|
console.log();
|
|
2848
3829
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
2849
3830
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -2870,6 +3851,7 @@ function handleChange(feature) {
|
|
|
2870
3851
|
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2871
3852
|
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2872
3853
|
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
3854
|
+
console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
|
|
2873
3855
|
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2874
3856
|
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
2875
3857
|
console.log();
|
|
@@ -2878,6 +3860,7 @@ function handleChange(feature) {
|
|
|
2878
3860
|
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2879
3861
|
printDimensionGuidance(dim, dimNames);
|
|
2880
3862
|
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
3863
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
2881
3864
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
2882
3865
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
2883
3866
|
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
@@ -2892,10 +3875,10 @@ function handleChange(feature) {
|
|
|
2892
3875
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
2893
3876
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
2894
3877
|
console.log();
|
|
2895
|
-
// If the change was initiated from a step before
|
|
2896
|
-
// instead of jumping to step
|
|
2897
|
-
// step
|
|
2898
|
-
if (currentStep <
|
|
3878
|
+
// If the change was initiated from a step before 15, return to that step
|
|
3879
|
+
// instead of jumping to step 15. The change workflow should only loop to
|
|
3880
|
+
// step 15 when changes are requested FROM step 15.
|
|
3881
|
+
if (currentStep < 15) {
|
|
2899
3882
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2900
3883
|
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2901
3884
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
@@ -2906,7 +3889,7 @@ function handleChange(feature) {
|
|
|
2906
3889
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2907
3890
|
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2908
3891
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2909
|
-
chalk.bold(`codeyam editor
|
|
3892
|
+
chalk.bold(`codeyam editor 15`));
|
|
2910
3893
|
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2911
3894
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2912
3895
|
}
|
|
@@ -2917,10 +3900,11 @@ function handleChange(feature) {
|
|
|
2917
3900
|
* Fetch the audit result from the server.
|
|
2918
3901
|
* Returns null if the server is unreachable.
|
|
2919
3902
|
*/
|
|
2920
|
-
async function fetchAuditResult() {
|
|
3903
|
+
async function fetchAuditResult(options) {
|
|
2921
3904
|
const port = getServerPort();
|
|
3905
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
2922
3906
|
try {
|
|
2923
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3907
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
2924
3908
|
if (!res.ok)
|
|
2925
3909
|
return null;
|
|
2926
3910
|
return await res.json();
|
|
@@ -2943,17 +3927,34 @@ async function checkAuditGate() {
|
|
|
2943
3927
|
return true; // Server unreachable — don't block
|
|
2944
3928
|
if (data.summary?.allPassing === true)
|
|
2945
3929
|
return true;
|
|
2946
|
-
//
|
|
2947
|
-
|
|
2948
|
-
|
|
3930
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3931
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3932
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3933
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3934
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3935
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3936
|
+
return true;
|
|
3937
|
+
}
|
|
3938
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
2949
3939
|
try {
|
|
2950
|
-
await
|
|
3940
|
+
const entities = await loadEntities({});
|
|
3941
|
+
if (entities && entities.length > 0) {
|
|
3942
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3943
|
+
const db = getDatabase();
|
|
3944
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3945
|
+
sha: e.sha,
|
|
3946
|
+
name: e.name,
|
|
3947
|
+
filePath: e.filePath || '',
|
|
3948
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3949
|
+
e.metadata?.namedExport === false,
|
|
3950
|
+
})));
|
|
3951
|
+
}
|
|
2951
3952
|
}
|
|
2952
3953
|
catch {
|
|
2953
|
-
|
|
3954
|
+
// Fall through
|
|
2954
3955
|
}
|
|
2955
|
-
// Re-check after
|
|
2956
|
-
const retry = await fetchAuditResult();
|
|
3956
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3957
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
2957
3958
|
if (retry?.summary?.allPassing === true)
|
|
2958
3959
|
return true;
|
|
2959
3960
|
}
|
|
@@ -2971,24 +3972,152 @@ function printAuditGateFailures(data) {
|
|
|
2971
3972
|
if (!s)
|
|
2972
3973
|
return;
|
|
2973
3974
|
const issues = [];
|
|
2974
|
-
if (s.componentsMissing > 0)
|
|
3975
|
+
if (s.componentsMissing > 0) {
|
|
2975
3976
|
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
2976
|
-
|
|
3977
|
+
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3978
|
+
for (const c of missing) {
|
|
3979
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3980
|
+
if (c.hint)
|
|
3981
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
if (s.componentsWithErrors > 0) {
|
|
2977
3985
|
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
2978
|
-
|
|
3986
|
+
const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
|
|
3987
|
+
for (const c of withErrors) {
|
|
3988
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
if (s.functionsMissing > 0) {
|
|
2979
3992
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
2980
|
-
|
|
3993
|
+
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3994
|
+
for (const f of missing) {
|
|
3995
|
+
if (f.testFile) {
|
|
3996
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
3997
|
+
}
|
|
3998
|
+
else {
|
|
3999
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
4000
|
+
if (f.suggestedTestFile) {
|
|
4001
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
if (s.functionsFailing > 0) {
|
|
2981
4007
|
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
2982
|
-
|
|
4008
|
+
const failing = (data.functions || []).filter((f) => f.status === 'failing');
|
|
4009
|
+
for (const f of failing) {
|
|
4010
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
if (s.functionsRunnerError > 0) {
|
|
2983
4014
|
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
2984
|
-
|
|
4015
|
+
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
4016
|
+
for (const f of runnerErrors) {
|
|
4017
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4018
|
+
if (f.hint)
|
|
4019
|
+
issues.push(` ${f.hint}`);
|
|
4020
|
+
else if (f.errorMessage)
|
|
4021
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
if (s.functionsNameMismatch > 0) {
|
|
2985
4025
|
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
2986
|
-
|
|
4026
|
+
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
4027
|
+
for (const f of mismatch) {
|
|
4028
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4029
|
+
if (f.hint)
|
|
4030
|
+
issues.push(` Fix: ${f.hint}`);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
if (s.missingFromGlossary > 0) {
|
|
2987
4034
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
4035
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
4036
|
+
for (const m of missingGlossary) {
|
|
4037
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
4038
|
+
}
|
|
4039
|
+
const missingPaths = missingGlossary
|
|
4040
|
+
.map((m) => m.filePath)
|
|
4041
|
+
.filter(Boolean);
|
|
4042
|
+
if (missingPaths.length > 0) {
|
|
4043
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
|
|
4044
|
+
}
|
|
4045
|
+
else {
|
|
4046
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
if (s.incompleteEntities > 0) {
|
|
4050
|
+
// Check for persistent analysis failures
|
|
4051
|
+
let analysisFailures = {};
|
|
4052
|
+
try {
|
|
4053
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
4054
|
+
if (fs.existsSync(failuresPath)) {
|
|
4055
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
catch {
|
|
4059
|
+
// Non-fatal
|
|
4060
|
+
}
|
|
4061
|
+
const preCount = s.preExistingIncompleteEntities || 0;
|
|
4062
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
4063
|
+
if (hasFailures) {
|
|
4064
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
4065
|
+
}
|
|
4066
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
4067
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
4068
|
+
}
|
|
4069
|
+
else if (preCount > 0) {
|
|
4070
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
|
|
4071
|
+
}
|
|
4072
|
+
else {
|
|
4073
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
4074
|
+
}
|
|
4075
|
+
const incomplete = data.incompleteEntities || [];
|
|
4076
|
+
for (const e of incomplete) {
|
|
4077
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4078
|
+
if (failureEntry) {
|
|
4079
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
4080
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
4081
|
+
}
|
|
4082
|
+
else {
|
|
4083
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
if (!hasFailures) {
|
|
4087
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (s.unassociatedScenarios > 0) {
|
|
4091
|
+
const unassociated = data.unassociatedScenarios || [];
|
|
4092
|
+
const unassocPaths = unassociated
|
|
4093
|
+
.map((u) => u.filePath)
|
|
4094
|
+
.filter(Boolean);
|
|
4095
|
+
if (unassocPaths.length > 0) {
|
|
4096
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
|
|
4097
|
+
}
|
|
4098
|
+
else {
|
|
4099
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
4100
|
+
}
|
|
4101
|
+
for (const u of unassociated) {
|
|
4102
|
+
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
if (s.miscategorizedScenarios > 0) {
|
|
2991
4106
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
4107
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
4108
|
+
for (const m of miscategorized) {
|
|
4109
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
4110
|
+
}
|
|
4111
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
4112
|
+
}
|
|
4113
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
4114
|
+
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
4115
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
4116
|
+
for (const r of recapture) {
|
|
4117
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
4118
|
+
}
|
|
4119
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
4120
|
+
}
|
|
2992
4121
|
if (issues.length > 0) {
|
|
2993
4122
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
2994
4123
|
for (const issue of issues) {
|
|
@@ -3011,8 +4140,6 @@ function printAuditGateFailures(data) {
|
|
|
3011
4140
|
}
|
|
3012
4141
|
}
|
|
3013
4142
|
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
3014
|
-
console.error(chalk.yellow('If errors reference browser APIs (localStorage, sessionStorage, window, document),'));
|
|
3015
|
-
console.error(chalk.yellow('create a universal mock: codeyam detect-universal-mocks'));
|
|
3016
4143
|
}
|
|
3017
4144
|
}
|
|
3018
4145
|
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
@@ -3025,59 +4152,137 @@ function printAuditGateFailures(data) {
|
|
|
3025
4152
|
* which glossary components have registered scenarios and which functions
|
|
3026
4153
|
* have test files. Exits with code 1 if anything is missing.
|
|
3027
4154
|
*/
|
|
3028
|
-
async function handleAudit() {
|
|
4155
|
+
async function handleAudit(options) {
|
|
3029
4156
|
let data = await fetchAuditResult();
|
|
3030
4157
|
if (!data) {
|
|
3031
4158
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3032
4159
|
process.exit(1);
|
|
3033
4160
|
}
|
|
3034
|
-
//
|
|
3035
|
-
//
|
|
3036
|
-
//
|
|
3037
|
-
//
|
|
4161
|
+
// Two-phase auto-fix for entity associations:
|
|
4162
|
+
// Phase 1: DB-only backfill — fast, matches existing entities to scenarios.
|
|
4163
|
+
// Phase 2: If backfill fails, targeted analyze-imports for only the
|
|
4164
|
+
// specific unresolved files (1-3 files, not the full glossary scan).
|
|
3038
4165
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
4166
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3039
4167
|
let autoRemediationFailed = false;
|
|
3040
|
-
if (incompleteBeforeFix.length > 0) {
|
|
3041
|
-
|
|
4168
|
+
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
4169
|
+
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
4170
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
4171
|
+
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3042
4172
|
try {
|
|
3043
|
-
await
|
|
4173
|
+
const entities = await loadEntities({});
|
|
4174
|
+
if (entities && entities.length > 0) {
|
|
4175
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4176
|
+
const db = getDatabase();
|
|
4177
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4178
|
+
sha: e.sha,
|
|
4179
|
+
name: e.name,
|
|
4180
|
+
filePath: e.filePath || '',
|
|
4181
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4182
|
+
e.metadata?.namedExport === false,
|
|
4183
|
+
})));
|
|
4184
|
+
}
|
|
3044
4185
|
}
|
|
3045
4186
|
catch {
|
|
3046
|
-
// Fall through —
|
|
4187
|
+
// Fall through — re-fetch will show remaining issues
|
|
3047
4188
|
}
|
|
3048
|
-
// Re-fetch audit results after the
|
|
3049
|
-
|
|
4189
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
4190
|
+
// since they haven't changed (backfill is DB-only).
|
|
4191
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3050
4192
|
if (!data) {
|
|
3051
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
4193
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3052
4194
|
process.exit(1);
|
|
3053
4195
|
}
|
|
3054
|
-
// If
|
|
3055
|
-
//
|
|
4196
|
+
// If issues persist after DB-only backfill, run targeted analyze-imports
|
|
4197
|
+
// for ONLY the specific files that need entities. This is fast (1-3 files)
|
|
4198
|
+
// unlike full analyze-imports which scans all glossary entries.
|
|
4199
|
+
const incompleteAfterBackfill = data.incompleteEntities || [];
|
|
4200
|
+
const unassociatedAfterBackfill = data.unassociatedScenarios || [];
|
|
4201
|
+
if (incompleteAfterBackfill.length > 0 ||
|
|
4202
|
+
unassociatedAfterBackfill.length > 0) {
|
|
4203
|
+
const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
|
|
4204
|
+
const targetPaths = determineTargetedAnalysisPaths({
|
|
4205
|
+
unassociatedScenarios: unassociatedAfterBackfill,
|
|
4206
|
+
incompleteEntities: incompleteAfterBackfill,
|
|
4207
|
+
});
|
|
4208
|
+
if (targetPaths.length > 0) {
|
|
4209
|
+
console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
|
|
4210
|
+
try {
|
|
4211
|
+
await handleAnalyzeImports({
|
|
4212
|
+
silent: true,
|
|
4213
|
+
filePaths: targetPaths,
|
|
4214
|
+
});
|
|
4215
|
+
// Retry backfill after analysis created entities
|
|
4216
|
+
const entities = await loadEntities({});
|
|
4217
|
+
if (entities && entities.length > 0) {
|
|
4218
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4219
|
+
const freshDb = getDb();
|
|
4220
|
+
await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
|
|
4221
|
+
sha: e.sha,
|
|
4222
|
+
name: e.name,
|
|
4223
|
+
filePath: e.filePath || '',
|
|
4224
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4225
|
+
e.metadata?.namedExport === false,
|
|
4226
|
+
})));
|
|
4227
|
+
}
|
|
4228
|
+
// Re-fetch audit results after targeted analysis
|
|
4229
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
4230
|
+
if (!data) {
|
|
4231
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
|
|
4232
|
+
process.exit(1);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
catch {
|
|
4236
|
+
// Targeted analysis failed — fall through to show remaining issues
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
// Check if issues persist after all remediation attempts
|
|
3056
4241
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3057
|
-
|
|
4242
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
4243
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3058
4244
|
autoRemediationFailed = true;
|
|
3059
4245
|
}
|
|
3060
4246
|
}
|
|
3061
|
-
|
|
4247
|
+
let { components, functions, summary } = data;
|
|
3062
4248
|
console.log();
|
|
3063
4249
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
3064
4250
|
console.log();
|
|
3065
4251
|
// Components
|
|
3066
4252
|
if (components.length > 0) {
|
|
4253
|
+
// Build name frequency map for disambiguation
|
|
4254
|
+
const componentNameCounts = new Map();
|
|
4255
|
+
for (const c of components) {
|
|
4256
|
+
componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
|
|
4257
|
+
}
|
|
3067
4258
|
console.log(chalk.bold('Components (scenarios):'));
|
|
3068
4259
|
for (const c of components) {
|
|
3069
|
-
const icon = c.status === 'ok'
|
|
4260
|
+
const icon = c.status === 'ok'
|
|
4261
|
+
? chalk.green('✓')
|
|
4262
|
+
: c.status === 'needs_recapture'
|
|
4263
|
+
? chalk.yellow('↻')
|
|
4264
|
+
: chalk.red('✗');
|
|
3070
4265
|
let detail;
|
|
3071
4266
|
if (c.status === 'has_errors') {
|
|
3072
4267
|
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
3073
4268
|
}
|
|
4269
|
+
else if (c.status === 'needs_recapture') {
|
|
4270
|
+
detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
|
|
4271
|
+
}
|
|
3074
4272
|
else if (c.status === 'ok') {
|
|
3075
4273
|
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
3076
4274
|
}
|
|
3077
4275
|
else {
|
|
3078
4276
|
detail = chalk.red(' — no scenarios registered');
|
|
4277
|
+
if (c.hint) {
|
|
4278
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
4279
|
+
}
|
|
3079
4280
|
}
|
|
3080
|
-
|
|
4281
|
+
// Show file path for failing components always, for OK only when name is ambiguous
|
|
4282
|
+
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
4283
|
+
const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
|
|
4284
|
+
const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
|
|
4285
|
+
console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
|
|
3081
4286
|
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
3082
4287
|
for (const err of c.clientErrors.slice(0, 3)) {
|
|
3083
4288
|
console.log(chalk.red(` → ${err}`));
|
|
@@ -3085,14 +4290,6 @@ async function handleAudit() {
|
|
|
3085
4290
|
if (c.clientErrors.length > 3) {
|
|
3086
4291
|
console.log(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
3087
4292
|
}
|
|
3088
|
-
// Detect browser API errors and provide actionable guidance
|
|
3089
|
-
const browserApiPattern = /\b(localStorage|sessionStorage|window\.|document\.|navigator\.|indexedDB|matchMedia|ResizeObserver|IntersectionObserver|MutationObserver)\b/;
|
|
3090
|
-
const hasBrowserApiErrors = c.clientErrors.some((err) => browserApiPattern.test(err));
|
|
3091
|
-
if (hasBrowserApiErrors) {
|
|
3092
|
-
console.log(chalk.yellow(` ⚠ These errors are caused by browser APIs that don't exist during server-side analysis.`));
|
|
3093
|
-
console.log(chalk.yellow(` Fix: Create a universal mock to stub the missing API. Run: codeyam detect-universal-mocks`));
|
|
3094
|
-
console.log(chalk.yellow(` DO NOT re-run the audit or analyze-imports — the error will persist until a mock is created.`));
|
|
3095
|
-
}
|
|
3096
4293
|
}
|
|
3097
4294
|
}
|
|
3098
4295
|
console.log();
|
|
@@ -3105,14 +4302,20 @@ async function handleAudit() {
|
|
|
3105
4302
|
let detail;
|
|
3106
4303
|
switch (f.status) {
|
|
3107
4304
|
case 'ok':
|
|
3108
|
-
|
|
4305
|
+
if (f.testCaseCount !== undefined && f.testCaseCount < 3) {
|
|
4306
|
+
detail = chalk.yellow(` (${f.testFile}) — ⚠ only ${f.testCaseCount} test case${f.testCaseCount !== 1 ? 's' : ''}, consider adding more`);
|
|
4307
|
+
}
|
|
4308
|
+
else {
|
|
4309
|
+
detail = chalk.dim(` (${f.testFile}${f.testCaseCount !== undefined ? `, ${f.testCaseCount} tests` : ''})`);
|
|
4310
|
+
}
|
|
3109
4311
|
break;
|
|
3110
4312
|
case 'runner_error':
|
|
3111
4313
|
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3112
4314
|
if (f.errorMessage) {
|
|
3113
4315
|
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3114
4316
|
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3115
|
-
|
|
4317
|
+
const ctx = getTechStackContext(process.cwd());
|
|
4318
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
3116
4319
|
}
|
|
3117
4320
|
break;
|
|
3118
4321
|
case 'failing':
|
|
@@ -3123,13 +4326,23 @@ async function handleAudit() {
|
|
|
3123
4326
|
break;
|
|
3124
4327
|
case 'missing':
|
|
3125
4328
|
default:
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
4329
|
+
if (f.testFile) {
|
|
4330
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
4331
|
+
}
|
|
4332
|
+
else {
|
|
4333
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
4334
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
4335
|
+
if (f.suggestedTestFile) {
|
|
4336
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
3129
4339
|
break;
|
|
3130
4340
|
}
|
|
3131
4341
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
3132
4342
|
}
|
|
4343
|
+
if (summary.functionsThinCoverage > 0) {
|
|
4344
|
+
console.log(chalk.yellow.bold(` ⚠ ${summary.functionsThinCoverage} function${summary.functionsThinCoverage !== 1 ? 's' : ''} with thin test coverage (< 3 test cases). Add more tests to cover all branches.`));
|
|
4345
|
+
}
|
|
3133
4346
|
console.log();
|
|
3134
4347
|
}
|
|
3135
4348
|
// Missing from glossary
|
|
@@ -3139,28 +4352,85 @@ async function handleAudit() {
|
|
|
3139
4352
|
for (const m of missingFromGlossary) {
|
|
3140
4353
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3141
4354
|
}
|
|
3142
|
-
|
|
4355
|
+
const mgPaths = missingFromGlossary
|
|
4356
|
+
.map((m) => m.filePath)
|
|
4357
|
+
.filter(Boolean);
|
|
4358
|
+
if (mgPaths.length > 0) {
|
|
4359
|
+
console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
|
|
4360
|
+
}
|
|
4361
|
+
else {
|
|
4362
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
4363
|
+
}
|
|
3143
4364
|
console.log();
|
|
3144
4365
|
}
|
|
3145
|
-
// Incomplete entities (scenarios without analyses)
|
|
4366
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
4367
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
4368
|
+
// template which is slow even for a few files. Users should run
|
|
4369
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3146
4370
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3147
4371
|
if (incompleteEntities.length > 0) {
|
|
4372
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
4373
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
4374
|
+
// Check for persistent analysis failures
|
|
4375
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3148
4376
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3149
4377
|
for (const e of incompleteEntities) {
|
|
3150
|
-
|
|
4378
|
+
// Check if this entity has a persistent failure
|
|
4379
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4380
|
+
if (failureEntry) {
|
|
4381
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
4382
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
4383
|
+
const guidance = formatManualAnalysisGuidance({
|
|
4384
|
+
name: e.name,
|
|
4385
|
+
filePath: filePath || '',
|
|
4386
|
+
scenarioCount: e.scenarioCount,
|
|
4387
|
+
error: failureEntry.error,
|
|
4388
|
+
});
|
|
4389
|
+
// Print each line with proper indentation
|
|
4390
|
+
for (const line of guidance.split('\n')) {
|
|
4391
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
else {
|
|
4395
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
4396
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4400
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
4401
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3151
4402
|
}
|
|
4403
|
+
console.log();
|
|
4404
|
+
}
|
|
4405
|
+
// Unassociated scenarios (NULL entity_sha with file paths)
|
|
4406
|
+
const unassociatedScenarios = data.unassociatedScenarios || [];
|
|
4407
|
+
if (unassociatedScenarios.length > 0) {
|
|
4408
|
+
console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
|
|
4409
|
+
for (const u of unassociatedScenarios) {
|
|
4410
|
+
console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
|
|
4411
|
+
for (const sn of u.scenarioNames.slice(0, 3)) {
|
|
4412
|
+
console.log(chalk.dim(` "${sn}"`));
|
|
4413
|
+
}
|
|
4414
|
+
if (u.scenarioNames.length > 3) {
|
|
4415
|
+
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
const uaPaths = unassociatedScenarios
|
|
4419
|
+
.map((u) => u.filePath)
|
|
4420
|
+
.filter(Boolean);
|
|
4421
|
+
const uaCmd = uaPaths.length > 0
|
|
4422
|
+
? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
|
|
4423
|
+
: 'codeyam editor analyze-imports';
|
|
3152
4424
|
if (autoRemediationFailed) {
|
|
3153
|
-
console.log(chalk.
|
|
3154
|
-
console.log(chalk.
|
|
3155
|
-
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3156
|
-
console.log(chalk.yellow(' • Entity code uses browser APIs (localStorage, window, document) — create a universal mock'));
|
|
3157
|
-
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3158
|
-
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3159
|
-
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
3160
|
-
console.log(chalk.yellow(' To fix browser API issues: run `codeyam detect-universal-mocks`'));
|
|
4425
|
+
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
4426
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
|
|
3161
4427
|
}
|
|
3162
4428
|
else {
|
|
3163
|
-
console.log(chalk.yellow(
|
|
4429
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
|
|
4430
|
+
}
|
|
4431
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4432
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
4433
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3164
4434
|
}
|
|
3165
4435
|
console.log();
|
|
3166
4436
|
}
|
|
@@ -3179,6 +4449,51 @@ async function handleAudit() {
|
|
|
3179
4449
|
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
3180
4450
|
console.log();
|
|
3181
4451
|
}
|
|
4452
|
+
// Scenarios needing recapture (entity or dependency tree changed)
|
|
4453
|
+
const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
|
|
4454
|
+
if (scenariosNeedingRecapture.length > 0) {
|
|
4455
|
+
console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
|
|
4456
|
+
for (const s of scenariosNeedingRecapture) {
|
|
4457
|
+
const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
|
|
4458
|
+
? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
|
|
4459
|
+
: `${s.status.status}`;
|
|
4460
|
+
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
4461
|
+
}
|
|
4462
|
+
if (options?.fix) {
|
|
4463
|
+
// --fix: auto-recapture stale scenarios instead of just reporting them
|
|
4464
|
+
const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
|
|
4465
|
+
if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
|
|
4466
|
+
console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
|
|
4467
|
+
console.log();
|
|
4468
|
+
await handleRecaptureStale();
|
|
4469
|
+
// Re-fetch audit results so the summary and exit code reflect
|
|
4470
|
+
// the post-fix state, not the pre-fix state.
|
|
4471
|
+
const refreshed = await fetchAuditResult({ skipTests: true });
|
|
4472
|
+
if (refreshed) {
|
|
4473
|
+
data = refreshed;
|
|
4474
|
+
({ components, functions, summary } = data);
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
else {
|
|
4479
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
4480
|
+
console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
|
|
4481
|
+
}
|
|
4482
|
+
console.log();
|
|
4483
|
+
}
|
|
4484
|
+
// Duplicate glossary names (warning, not a failure)
|
|
4485
|
+
const duplicateNames = data.duplicateNames || [];
|
|
4486
|
+
if (duplicateNames.length > 0) {
|
|
4487
|
+
console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
|
|
4488
|
+
for (const dn of duplicateNames) {
|
|
4489
|
+
console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
|
|
4490
|
+
for (const fp of dn.filePaths) {
|
|
4491
|
+
console.log(` ${fp}`);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
|
|
4495
|
+
console.log();
|
|
4496
|
+
}
|
|
3182
4497
|
// Summary
|
|
3183
4498
|
const allOk = summary.allPassing;
|
|
3184
4499
|
if (allOk) {
|
|
@@ -3211,25 +4526,123 @@ async function handleAudit() {
|
|
|
3211
4526
|
if (summary.incompleteEntities > 0) {
|
|
3212
4527
|
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3213
4528
|
}
|
|
4529
|
+
if (summary.unassociatedScenarios > 0) {
|
|
4530
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
4531
|
+
}
|
|
3214
4532
|
if (summary.miscategorizedScenarios > 0) {
|
|
3215
4533
|
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3216
4534
|
}
|
|
4535
|
+
if (summary.scenariosNeedingRecapture > 0) {
|
|
4536
|
+
parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
|
|
4537
|
+
}
|
|
3217
4538
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
3218
4539
|
}
|
|
3219
4540
|
console.log();
|
|
3220
4541
|
if (!allOk) {
|
|
3221
4542
|
process.exit(1);
|
|
3222
4543
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
4544
|
+
}
|
|
4545
|
+
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
4546
|
+
/**
|
|
4547
|
+
* `codeyam editor recapture-stale`
|
|
4548
|
+
*
|
|
4549
|
+
* Identifies all scenarios whose entity (or dependency) has changed but
|
|
4550
|
+
* whose screenshot hasn't been recaptured this session, then recaptures
|
|
4551
|
+
* them in batch.
|
|
4552
|
+
*/
|
|
4553
|
+
async function handleRecaptureStale() {
|
|
4554
|
+
const port = getServerPort();
|
|
4555
|
+
const url = `http://localhost:${port}/api/editor-recapture-stale`;
|
|
4556
|
+
console.log();
|
|
4557
|
+
console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
|
|
4558
|
+
console.log();
|
|
4559
|
+
let res;
|
|
3227
4560
|
try {
|
|
3228
|
-
await
|
|
4561
|
+
res = await fetch(url, { method: 'POST' });
|
|
3229
4562
|
}
|
|
3230
|
-
catch {
|
|
3231
|
-
|
|
3232
|
-
console.
|
|
4563
|
+
catch (err) {
|
|
4564
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
4565
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
4566
|
+
process.exit(1);
|
|
4567
|
+
}
|
|
4568
|
+
if (!res.ok) {
|
|
4569
|
+
const body = await res.json().catch(() => null);
|
|
4570
|
+
console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
|
|
4571
|
+
if (body?.error)
|
|
4572
|
+
console.error(chalk.red(` ${body.error}`));
|
|
4573
|
+
process.exit(1);
|
|
4574
|
+
}
|
|
4575
|
+
// Handle JSON response (early returns like "no changes" / "all up to date")
|
|
4576
|
+
const contentType = res.headers.get('content-type') || '';
|
|
4577
|
+
if (contentType.includes('application/json')) {
|
|
4578
|
+
const data = await res.json();
|
|
4579
|
+
if (data.note) {
|
|
4580
|
+
console.log(chalk.dim(data.note));
|
|
4581
|
+
}
|
|
4582
|
+
else if (data.total === 0) {
|
|
4583
|
+
console.log(chalk.green('All scenarios are up to date.'));
|
|
4584
|
+
}
|
|
4585
|
+
console.log();
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
// Stream NDJSON progress events
|
|
4589
|
+
let total = 0;
|
|
4590
|
+
let recapturedCount = 0;
|
|
4591
|
+
let failedCount = 0;
|
|
4592
|
+
const reader = res.body?.getReader();
|
|
4593
|
+
if (!reader) {
|
|
4594
|
+
console.error(chalk.red('Error: No response body'));
|
|
4595
|
+
process.exit(1);
|
|
4596
|
+
}
|
|
4597
|
+
const decoder = new TextDecoder();
|
|
4598
|
+
let buffer = '';
|
|
4599
|
+
while (true) {
|
|
4600
|
+
const { done, value } = await reader.read();
|
|
4601
|
+
if (done)
|
|
4602
|
+
break;
|
|
4603
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4604
|
+
const lines = buffer.split('\n');
|
|
4605
|
+
buffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
4606
|
+
for (const line of lines) {
|
|
4607
|
+
if (!line.trim())
|
|
4608
|
+
continue;
|
|
4609
|
+
try {
|
|
4610
|
+
const event = JSON.parse(line);
|
|
4611
|
+
switch (event.type) {
|
|
4612
|
+
case 'start':
|
|
4613
|
+
total = event.total;
|
|
4614
|
+
console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
|
|
4615
|
+
break;
|
|
4616
|
+
case 'capturing':
|
|
4617
|
+
process.stdout.write(chalk.dim(` … ${event.name}`));
|
|
4618
|
+
break;
|
|
4619
|
+
case 'success':
|
|
4620
|
+
// Clear the "capturing" line and print success
|
|
4621
|
+
process.stdout.write('\r\x1b[K');
|
|
4622
|
+
console.log(` ${chalk.green('✓')} ${event.name}`);
|
|
4623
|
+
recapturedCount++;
|
|
4624
|
+
break;
|
|
4625
|
+
case 'failure':
|
|
4626
|
+
process.stdout.write('\r\x1b[K');
|
|
4627
|
+
console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
|
|
4628
|
+
failedCount++;
|
|
4629
|
+
break;
|
|
4630
|
+
case 'done':
|
|
4631
|
+
// Final summary
|
|
4632
|
+
console.log();
|
|
4633
|
+
const color = failedCount > 0 ? chalk.yellow : chalk.green;
|
|
4634
|
+
console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
|
|
4635
|
+
console.log();
|
|
4636
|
+
break;
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
catch {
|
|
4640
|
+
// Skip unparseable lines
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
if (failedCount > 0) {
|
|
4645
|
+
process.exit(1);
|
|
3233
4646
|
}
|
|
3234
4647
|
}
|
|
3235
4648
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
@@ -3334,14 +4747,14 @@ async function handleScenarioCoverage() {
|
|
|
3334
4747
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3335
4748
|
try {
|
|
3336
4749
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3337
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4750
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3338
4751
|
const db = getDatabase();
|
|
3339
4752
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3340
4753
|
if (backfillCount > 0) {
|
|
3341
4754
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3342
4755
|
await handleAnalyzeImports({ silent: true });
|
|
3343
4756
|
// Run backfill after analysis
|
|
3344
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4757
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3345
4758
|
const entities = await loadEntities({});
|
|
3346
4759
|
if (entities && entities.length > 0) {
|
|
3347
4760
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -3479,7 +4892,7 @@ async function handleTemplate() {
|
|
|
3479
4892
|
console.log(chalk.green(' Git initialized.'));
|
|
3480
4893
|
}
|
|
3481
4894
|
// 4. Run codeyam init
|
|
3482
|
-
console.log(chalk.bold('
|
|
4895
|
+
console.log(chalk.bold('Initializing project...'));
|
|
3483
4896
|
await initCommand.handler({
|
|
3484
4897
|
force: true,
|
|
3485
4898
|
'keep-server': true,
|
|
@@ -3488,7 +4901,7 @@ async function handleTemplate() {
|
|
|
3488
4901
|
_: [],
|
|
3489
4902
|
});
|
|
3490
4903
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
3491
|
-
// 5. Verify config has startCommand
|
|
4904
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
3492
4905
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3493
4906
|
if (fs.existsSync(configPath)) {
|
|
3494
4907
|
try {
|
|
@@ -3499,6 +4912,30 @@ async function handleTemplate() {
|
|
|
3499
4912
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
3500
4913
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
3501
4914
|
}
|
|
4915
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4916
|
+
if (stack?.supportedFormats) {
|
|
4917
|
+
config.appFormats = stack.supportedFormats;
|
|
4918
|
+
}
|
|
4919
|
+
// Pre-populate tech stack from template
|
|
4920
|
+
if (stack) {
|
|
4921
|
+
config.techStack = getTechStackForTemplate(stack.id);
|
|
4922
|
+
}
|
|
4923
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4924
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4925
|
+
config.defaultScreenSize = {
|
|
4926
|
+
name: 'iPhone 16',
|
|
4927
|
+
width: 393,
|
|
4928
|
+
height: 852,
|
|
4929
|
+
};
|
|
4930
|
+
config.screenSizes = {
|
|
4931
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4932
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4933
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4934
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4935
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4936
|
+
};
|
|
4937
|
+
}
|
|
4938
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3502
4939
|
}
|
|
3503
4940
|
catch {
|
|
3504
4941
|
// Config parse error is non-fatal
|
|
@@ -3533,7 +4970,15 @@ async function handleTemplate() {
|
|
|
3533
4970
|
}
|
|
3534
4971
|
console.log();
|
|
3535
4972
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
3536
|
-
|
|
4973
|
+
if (stack?.id === 'expo-react-native') {
|
|
4974
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
4975
|
+
}
|
|
4976
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
4977
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
4978
|
+
}
|
|
4979
|
+
else {
|
|
4980
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
4981
|
+
}
|
|
3537
4982
|
}
|
|
3538
4983
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
3539
4984
|
/**
|
|
@@ -3558,7 +5003,7 @@ async function handleSync() {
|
|
|
3558
5003
|
// fall through
|
|
3559
5004
|
}
|
|
3560
5005
|
if (!projectSlug) {
|
|
3561
|
-
console.error(chalk.red('Error: No project slug found. Run codeyam
|
|
5006
|
+
console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
|
|
3562
5007
|
process.exit(1);
|
|
3563
5008
|
}
|
|
3564
5009
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -3742,7 +5187,7 @@ function handleEditorDebug(args) {
|
|
|
3742
5187
|
scenarios.push({
|
|
3743
5188
|
id: 'overview-with-state',
|
|
3744
5189
|
title: 'Cycle overview (project, with state)',
|
|
3745
|
-
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(
|
|
5190
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
|
|
3746
5191
|
});
|
|
3747
5192
|
}
|
|
3748
5193
|
const stepFns = {
|
|
@@ -3762,8 +5207,10 @@ function handleEditorDebug(args) {
|
|
|
3762
5207
|
14: printStep14,
|
|
3763
5208
|
15: printStep15,
|
|
3764
5209
|
16: printStep16,
|
|
5210
|
+
17: printStep17,
|
|
5211
|
+
18: printStep18,
|
|
3765
5212
|
};
|
|
3766
|
-
for (let step = 1; step <=
|
|
5213
|
+
for (let step = 1; step <= 18; step++) {
|
|
3767
5214
|
const stepId = `step-${step}`;
|
|
3768
5215
|
if (!wants(stepId))
|
|
3769
5216
|
continue;
|
|
@@ -3783,7 +5230,7 @@ function handleEditorDebug(args) {
|
|
|
3783
5230
|
if (step === 2) {
|
|
3784
5231
|
scenarios.push({
|
|
3785
5232
|
id: 'step-2-scaffold',
|
|
3786
|
-
title: 'Step 2 (
|
|
5233
|
+
title: 'Step 2 (Prepare) — scaffold flow (no project)',
|
|
3787
5234
|
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
|
|
3788
5235
|
});
|
|
3789
5236
|
}
|
|
@@ -3836,14 +5283,205 @@ function handleEditorDebug(args) {
|
|
|
3836
5283
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
3837
5284
|
console.log();
|
|
3838
5285
|
}
|
|
5286
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
5287
|
+
/**
|
|
5288
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
5289
|
+
*
|
|
5290
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
5291
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
5292
|
+
* needing the analyzer template to parse the source file.
|
|
5293
|
+
*/
|
|
5294
|
+
async function handleManualEntity(jsonArg) {
|
|
5295
|
+
const root = getProjectRoot();
|
|
5296
|
+
// Parse JSON input (supports @file.json convention)
|
|
5297
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
5298
|
+
if (parsed.error || !parsed.body) {
|
|
5299
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
5300
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
5301
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
5302
|
+
process.exit(1);
|
|
5303
|
+
}
|
|
5304
|
+
const input = parsed.body;
|
|
5305
|
+
// Validate input
|
|
5306
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
5307
|
+
// Read glossary for validation
|
|
5308
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
5309
|
+
let glossaryEntries = [];
|
|
5310
|
+
try {
|
|
5311
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
5312
|
+
}
|
|
5313
|
+
catch {
|
|
5314
|
+
// Empty glossary — validation will still check file existence
|
|
5315
|
+
}
|
|
5316
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
5317
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
5318
|
+
});
|
|
5319
|
+
if (errors.length > 0) {
|
|
5320
|
+
console.error(chalk.red('Validation errors:'));
|
|
5321
|
+
for (const err of errors) {
|
|
5322
|
+
console.error(chalk.red(` • ${err}`));
|
|
5323
|
+
}
|
|
5324
|
+
process.exit(1);
|
|
5325
|
+
}
|
|
5326
|
+
// Read source file and compute SHA
|
|
5327
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
5328
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
5329
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
5330
|
+
// Get project and branch
|
|
5331
|
+
await initializeEnvironment();
|
|
5332
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
5333
|
+
let projectSlug;
|
|
5334
|
+
try {
|
|
5335
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
5336
|
+
projectSlug = config.projectSlug;
|
|
5337
|
+
}
|
|
5338
|
+
catch {
|
|
5339
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
5340
|
+
process.exit(1);
|
|
5341
|
+
}
|
|
5342
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
5343
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
5344
|
+
const db = getDatabase();
|
|
5345
|
+
// Convert type info to dataForMocks format
|
|
5346
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
5347
|
+
// Create entity record
|
|
5348
|
+
const entityMetadata = {
|
|
5349
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
5350
|
+
name: ie.name,
|
|
5351
|
+
filePath: ie.filePath,
|
|
5352
|
+
})),
|
|
5353
|
+
manuallyAnalyzed: true,
|
|
5354
|
+
};
|
|
5355
|
+
// Check if entity already exists
|
|
5356
|
+
const existingEntity = await db
|
|
5357
|
+
.selectFrom('entities')
|
|
5358
|
+
.select('sha')
|
|
5359
|
+
.where('sha', '=', entitySha)
|
|
5360
|
+
.executeTakeFirst();
|
|
5361
|
+
if (!existingEntity) {
|
|
5362
|
+
await db
|
|
5363
|
+
.insertInto('entities')
|
|
5364
|
+
.values({
|
|
5365
|
+
sha: entitySha,
|
|
5366
|
+
project_id: project.id,
|
|
5367
|
+
name: input.name,
|
|
5368
|
+
entity_type: input.entityType,
|
|
5369
|
+
file_path: input.filePath,
|
|
5370
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5371
|
+
})
|
|
5372
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
5373
|
+
.execute();
|
|
5374
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5375
|
+
}
|
|
5376
|
+
else {
|
|
5377
|
+
// Update metadata on existing entity
|
|
5378
|
+
await db
|
|
5379
|
+
.updateTable('entities')
|
|
5380
|
+
.set({
|
|
5381
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5382
|
+
})
|
|
5383
|
+
.where('sha', '=', entitySha)
|
|
5384
|
+
.execute();
|
|
5385
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5386
|
+
}
|
|
5387
|
+
// Create entity_branch record
|
|
5388
|
+
await db
|
|
5389
|
+
.insertInto('entity_branches')
|
|
5390
|
+
.values({
|
|
5391
|
+
entity_sha: entitySha,
|
|
5392
|
+
branch_id: branch.id,
|
|
5393
|
+
active: true,
|
|
5394
|
+
})
|
|
5395
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
5396
|
+
.execute();
|
|
5397
|
+
// Create analysis record
|
|
5398
|
+
const { randomUUID } = await import('crypto');
|
|
5399
|
+
const analysisId = randomUUID();
|
|
5400
|
+
const now = new Date().toISOString();
|
|
5401
|
+
const analysisMetadata = {
|
|
5402
|
+
scenariosDataStructure: {
|
|
5403
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
5404
|
+
},
|
|
5405
|
+
mergedDataStructure: {
|
|
5406
|
+
dependencySchemas: null,
|
|
5407
|
+
},
|
|
5408
|
+
manuallyAnalyzed: true,
|
|
5409
|
+
};
|
|
5410
|
+
const analysisStatus = {
|
|
5411
|
+
startedAt: now,
|
|
5412
|
+
finishedAt: now,
|
|
5413
|
+
steps: [],
|
|
5414
|
+
errors: [],
|
|
5415
|
+
};
|
|
5416
|
+
// Check if analysis already exists for this entity
|
|
5417
|
+
const existingAnalysis = await db
|
|
5418
|
+
.selectFrom('analyses')
|
|
5419
|
+
.select('id')
|
|
5420
|
+
.where('entity_sha', '=', entitySha)
|
|
5421
|
+
.executeTakeFirst();
|
|
5422
|
+
if (!existingAnalysis) {
|
|
5423
|
+
await db
|
|
5424
|
+
.insertInto('analyses')
|
|
5425
|
+
.values({
|
|
5426
|
+
id: analysisId,
|
|
5427
|
+
project_id: project.id,
|
|
5428
|
+
entity_sha: entitySha,
|
|
5429
|
+
entity_name: input.name,
|
|
5430
|
+
entity_type: input.entityType,
|
|
5431
|
+
file_path: input.filePath,
|
|
5432
|
+
status: JSON.stringify(analysisStatus),
|
|
5433
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5434
|
+
})
|
|
5435
|
+
.execute();
|
|
5436
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
5437
|
+
}
|
|
5438
|
+
else {
|
|
5439
|
+
// Update existing analysis with manual metadata
|
|
5440
|
+
await db
|
|
5441
|
+
.updateTable('analyses')
|
|
5442
|
+
.set({
|
|
5443
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5444
|
+
status: JSON.stringify(analysisStatus),
|
|
5445
|
+
})
|
|
5446
|
+
.where('entity_sha', '=', entitySha)
|
|
5447
|
+
.execute();
|
|
5448
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
5449
|
+
}
|
|
5450
|
+
// Backfill entity_sha on scenarios
|
|
5451
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
5452
|
+
{
|
|
5453
|
+
sha: entitySha,
|
|
5454
|
+
name: input.name,
|
|
5455
|
+
filePath: input.filePath,
|
|
5456
|
+
isDefaultExport: false,
|
|
5457
|
+
},
|
|
5458
|
+
]);
|
|
5459
|
+
if (backfillResult.updated > 0) {
|
|
5460
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
5461
|
+
}
|
|
5462
|
+
// Clear from analysis failures tracker
|
|
5463
|
+
const failures = readAnalysisFailures(root);
|
|
5464
|
+
if (failures[input.filePath]) {
|
|
5465
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
5466
|
+
writeAnalysisFailures(root, updated);
|
|
5467
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
5468
|
+
}
|
|
5469
|
+
console.log();
|
|
5470
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
5471
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
5472
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
5473
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
5474
|
+
console.log();
|
|
5475
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
5476
|
+
}
|
|
3839
5477
|
// ─── Command definition ───────────────────────────────────────────────
|
|
3840
5478
|
const editorCommand = {
|
|
3841
5479
|
command: 'editor [step] [json]',
|
|
3842
5480
|
describe: 'Editor mode guided workflow',
|
|
3843
5481
|
builder: (yargs) => {
|
|
3844
5482
|
const stepDescription = IS_INTERNAL_BUILD
|
|
3845
|
-
? 'Step number (1-
|
|
3846
|
-
: 'Step number (1-
|
|
5483
|
+
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, design-system, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
|
|
5484
|
+
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, design-system, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
3847
5485
|
let builder = yargs
|
|
3848
5486
|
.positional('step', {
|
|
3849
5487
|
type: 'string',
|
|
@@ -3874,12 +5512,17 @@ const editorCommand = {
|
|
|
3874
5512
|
alias: 'p',
|
|
3875
5513
|
describe: 'Port to run the web server on',
|
|
3876
5514
|
default: 3111,
|
|
5515
|
+
})
|
|
5516
|
+
.option('fix', {
|
|
5517
|
+
type: 'boolean',
|
|
5518
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
5519
|
+
default: false,
|
|
3877
5520
|
});
|
|
3878
5521
|
if (IS_INTERNAL_BUILD) {
|
|
3879
5522
|
builder = builder
|
|
3880
5523
|
.option('target', {
|
|
3881
5524
|
type: 'string',
|
|
3882
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
5525
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
|
|
3883
5526
|
})
|
|
3884
5527
|
.option('resume', {
|
|
3885
5528
|
type: 'boolean',
|
|
@@ -3904,6 +5547,18 @@ const editorCommand = {
|
|
|
3904
5547
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
3905
5548
|
// journal, journal-update, dev-server, client-errors
|
|
3906
5549
|
if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
|
|
5550
|
+
// Guard: commit requires step 16 to have been run first.
|
|
5551
|
+
// Without this, Claude shortcuts the process by running
|
|
5552
|
+
// `codeyam editor commit` directly from step 15, skipping
|
|
5553
|
+
// steps 16-18 entirely.
|
|
5554
|
+
if (argv.step === 'commit') {
|
|
5555
|
+
const state = readState(root);
|
|
5556
|
+
if (state && state.step !== 16) {
|
|
5557
|
+
console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
|
|
5558
|
+
console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
|
|
5559
|
+
process.exit(1);
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
3907
5562
|
const port = getServerPort();
|
|
3908
5563
|
try {
|
|
3909
5564
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -3923,6 +5578,12 @@ const editorCommand = {
|
|
|
3923
5578
|
console.log(JSON.stringify(result.data, null, 2));
|
|
3924
5579
|
}
|
|
3925
5580
|
}
|
|
5581
|
+
// After a successful commit, remind Claude to continue to step 17
|
|
5582
|
+
if (argv.step === 'commit' && result.ok) {
|
|
5583
|
+
console.log();
|
|
5584
|
+
console.log(chalk.green('Commit done. Now run: ') +
|
|
5585
|
+
chalk.bold('codeyam editor 17'));
|
|
5586
|
+
}
|
|
3926
5587
|
}
|
|
3927
5588
|
catch (err) {
|
|
3928
5589
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
@@ -3931,14 +5592,73 @@ const editorCommand = {
|
|
|
3931
5592
|
}
|
|
3932
5593
|
return;
|
|
3933
5594
|
}
|
|
5595
|
+
// Subcommand: codeyam editor handoff '{"summary":"..."}'
|
|
5596
|
+
if (argv.step === 'handoff') {
|
|
5597
|
+
await handleHandoff(argv.json || '');
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
3934
5600
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
3935
5601
|
if (argv.step === 'register') {
|
|
3936
5602
|
await handleRegister(argv.json || '');
|
|
3937
5603
|
return;
|
|
3938
5604
|
}
|
|
3939
|
-
// Subcommand: codeyam editor
|
|
5605
|
+
// Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
|
|
5606
|
+
if (argv.step === 'glossary-add') {
|
|
5607
|
+
await handleGlossaryAdd(argv.json || '');
|
|
5608
|
+
return;
|
|
5609
|
+
}
|
|
5610
|
+
// Subcommand: codeyam editor task-ontrack
|
|
5611
|
+
// Corrective command when Claude advanced without creating a task.
|
|
5612
|
+
if (argv.step === 'task-ontrack') {
|
|
5613
|
+
const state = readState(root);
|
|
5614
|
+
if (!state?.step) {
|
|
5615
|
+
console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
|
|
5616
|
+
process.exit(1);
|
|
5617
|
+
}
|
|
5618
|
+
const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
|
|
5619
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
5620
|
+
const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
|
|
5621
|
+
console.log();
|
|
5622
|
+
console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
|
|
5623
|
+
console.log();
|
|
5624
|
+
console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
|
|
5625
|
+
console.log();
|
|
5626
|
+
// Print the TASK directive for the current step
|
|
5627
|
+
let taskTitle;
|
|
5628
|
+
if (state.step < totalSteps) {
|
|
5629
|
+
taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
|
|
5630
|
+
}
|
|
5631
|
+
else {
|
|
5632
|
+
taskTitle =
|
|
5633
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
5634
|
+
}
|
|
5635
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
5636
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
5637
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
5638
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
5639
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
5640
|
+
console.log();
|
|
5641
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
5642
|
+
console.log();
|
|
5643
|
+
console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
|
|
5644
|
+
console.log();
|
|
5645
|
+
// Mark task as expected-created so the next step can proceed.
|
|
5646
|
+
// The hook will set taskCreated=true when it sees the actual TaskCreate call.
|
|
5647
|
+
// But we also allow advancement now since task-ontrack itself is the corrective action.
|
|
5648
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
5649
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
5650
|
+
return;
|
|
5651
|
+
}
|
|
5652
|
+
// Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
|
|
3940
5653
|
if (argv.step === 'analyze-imports') {
|
|
3941
|
-
await
|
|
5654
|
+
const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
|
|
5655
|
+
const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
|
|
5656
|
+
await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
|
|
5657
|
+
return;
|
|
5658
|
+
}
|
|
5659
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
5660
|
+
if (argv.step === 'manual-entity') {
|
|
5661
|
+
await handleManualEntity(argv.json || '');
|
|
3942
5662
|
return;
|
|
3943
5663
|
}
|
|
3944
5664
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -3946,9 +5666,9 @@ const editorCommand = {
|
|
|
3946
5666
|
await handleDependents(argv.json || '');
|
|
3947
5667
|
return;
|
|
3948
5668
|
}
|
|
3949
|
-
// Subcommand: codeyam editor audit
|
|
5669
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
3950
5670
|
if (argv.step === 'audit') {
|
|
3951
|
-
await handleAudit();
|
|
5671
|
+
await handleAudit({ fix: argv.fix || false });
|
|
3952
5672
|
return;
|
|
3953
5673
|
}
|
|
3954
5674
|
// Subcommand: codeyam editor scenarios
|
|
@@ -3961,6 +5681,11 @@ const editorCommand = {
|
|
|
3961
5681
|
await handleScenarioCoverage();
|
|
3962
5682
|
return;
|
|
3963
5683
|
}
|
|
5684
|
+
// Subcommand: codeyam editor recapture-stale
|
|
5685
|
+
if (argv.step === 'recapture-stale') {
|
|
5686
|
+
await handleRecaptureStale();
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
3964
5689
|
// Subcommand: codeyam editor change <feature>
|
|
3965
5690
|
if (argv.step === 'change') {
|
|
3966
5691
|
handleChange(argv.json || '');
|
|
@@ -3976,6 +5701,11 @@ const editorCommand = {
|
|
|
3976
5701
|
await handleValidateSeed(argv.json || '');
|
|
3977
5702
|
return;
|
|
3978
5703
|
}
|
|
5704
|
+
// Subcommand: codeyam editor design-system <id>
|
|
5705
|
+
if (argv.step === 'design-system') {
|
|
5706
|
+
handleDesignSystem(argv.json || '');
|
|
5707
|
+
return;
|
|
5708
|
+
}
|
|
3979
5709
|
// Subcommand: codeyam editor delete <scenarioId>
|
|
3980
5710
|
if (argv.step === 'delete') {
|
|
3981
5711
|
await handleDelete(argv.json || '');
|
|
@@ -4020,18 +5750,13 @@ const editorCommand = {
|
|
|
4020
5750
|
}
|
|
4021
5751
|
else {
|
|
4022
5752
|
const state = readState(root);
|
|
4023
|
-
// Clear prompt file when feature is done (step 16) so the hook
|
|
4024
|
-
// can capture the next feature request from the user.
|
|
4025
|
-
if (state?.step === 16) {
|
|
4026
|
-
clearEditorUserPrompt(root);
|
|
4027
|
-
}
|
|
4028
5753
|
printCycleOverview(root, state);
|
|
4029
5754
|
}
|
|
4030
5755
|
return;
|
|
4031
5756
|
}
|
|
4032
5757
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
4033
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
4034
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
5758
|
+
if (step != null && (isNaN(step) || step < 1 || step > 18)) {
|
|
5759
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
|
|
4035
5760
|
process.exit(1);
|
|
4036
5761
|
}
|
|
4037
5762
|
if (step == null) {
|
|
@@ -4055,7 +5780,7 @@ const editorCommand = {
|
|
|
4055
5780
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4056
5781
|
const { projectSlug } = config;
|
|
4057
5782
|
if (!projectSlug) {
|
|
4058
|
-
errorLog('Missing project slug. Try reinitializing with: codeyam
|
|
5783
|
+
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
4059
5784
|
return;
|
|
4060
5785
|
}
|
|
4061
5786
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -4181,13 +5906,24 @@ const editorCommand = {
|
|
|
4181
5906
|
try {
|
|
4182
5907
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4183
5908
|
const seedDb = getDb();
|
|
4184
|
-
|
|
5909
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5910
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5911
|
+
const homeScenario = await seedDb
|
|
4185
5912
|
.selectFrom('editor_scenarios')
|
|
4186
5913
|
.select(['id', 'name', 'type'])
|
|
4187
5914
|
.where('project_id', '=', project.id)
|
|
4188
|
-
.where('
|
|
5915
|
+
.where('url', '=', '/')
|
|
5916
|
+
.where('component_name', 'is', null)
|
|
4189
5917
|
.orderBy('created_at', 'asc')
|
|
4190
5918
|
.executeTakeFirst();
|
|
5919
|
+
const appScenario = homeScenario ||
|
|
5920
|
+
(await seedDb
|
|
5921
|
+
.selectFrom('editor_scenarios')
|
|
5922
|
+
.select(['id', 'name', 'type'])
|
|
5923
|
+
.where('project_id', '=', project.id)
|
|
5924
|
+
.where('type', '=', 'application')
|
|
5925
|
+
.orderBy('created_at', 'asc')
|
|
5926
|
+
.executeTakeFirst());
|
|
4191
5927
|
if (appScenario) {
|
|
4192
5928
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4193
5929
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4330,7 +6066,7 @@ const editorCommand = {
|
|
|
4330
6066
|
if (!needsAnalysis) {
|
|
4331
6067
|
try {
|
|
4332
6068
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4333
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
6069
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4334
6070
|
const db = getDatabase();
|
|
4335
6071
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4336
6072
|
if (backfillCount > 0) {
|
|
@@ -4369,7 +6105,7 @@ const editorCommand = {
|
|
|
4369
6105
|
.execute();
|
|
4370
6106
|
if (unresolved.length > 0) {
|
|
4371
6107
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4372
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
6108
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4373
6109
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4374
6110
|
let pfpResolved = 0;
|
|
4375
6111
|
for (const row of unresolved) {
|
|
@@ -4452,12 +6188,13 @@ const editorCommand = {
|
|
|
4452
6188
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4453
6189
|
const skipValidation = step === 2 && argv.feature;
|
|
4454
6190
|
if (!skipValidation) {
|
|
4455
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
6191
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4456
6192
|
if (stepError) {
|
|
4457
6193
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4458
6194
|
process.exit(1);
|
|
4459
6195
|
}
|
|
4460
6196
|
}
|
|
6197
|
+
printHandoffContext(root);
|
|
4461
6198
|
switch (step) {
|
|
4462
6199
|
case 1: {
|
|
4463
6200
|
const feature = argv.feature || undefined;
|
|
@@ -4492,19 +6229,20 @@ const editorCommand = {
|
|
|
4492
6229
|
case 13:
|
|
4493
6230
|
case 14:
|
|
4494
6231
|
case 15:
|
|
4495
|
-
case 16:
|
|
6232
|
+
case 16:
|
|
6233
|
+
case 17:
|
|
6234
|
+
case 18: {
|
|
4496
6235
|
const feature = argv.feature || state?.feature;
|
|
4497
6236
|
if (!feature) {
|
|
4498
6237
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
4499
6238
|
process.exit(1);
|
|
4500
6239
|
}
|
|
4501
|
-
// Hard gate: steps
|
|
4502
|
-
if (step >=
|
|
6240
|
+
// Hard gate: steps 10+ require audit to have passed
|
|
6241
|
+
if (step >= 10) {
|
|
4503
6242
|
const auditOk = await checkAuditGate();
|
|
4504
6243
|
if (!auditOk) {
|
|
4505
6244
|
// checkAuditGate() already printed specific failure details above
|
|
4506
6245
|
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.'));
|
|
4508
6246
|
process.exit(1);
|
|
4509
6247
|
}
|
|
4510
6248
|
}
|
|
@@ -4523,6 +6261,8 @@ const editorCommand = {
|
|
|
4523
6261
|
14: printStep14,
|
|
4524
6262
|
15: printStep15,
|
|
4525
6263
|
16: printStep16,
|
|
6264
|
+
17: printStep17,
|
|
6265
|
+
18: printStep18,
|
|
4526
6266
|
};
|
|
4527
6267
|
stepFns[step](root, feature);
|
|
4528
6268
|
break;
|