@codeyam/codeyam-cli 0.1.0-staging.50df560 → 0.1.0-staging.5369cbb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +8 -8
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +3 -3
- package/analyzer-template/packages/ai/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 +1 -0
- 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 +1 -1
- package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +7 -1
- package/analyzer-template/packages/database/src/lib/loadEntity.ts +11 -4
- 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 +4 -4
- 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/runMultiScenarioServer.ts +26 -3
- 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/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__/init.gitignore.test.js +39 -3
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +1436 -205
- 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 +1969 -761
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- 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 +1108 -0
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +120 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +140 -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 +33 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/envFile.test.js +125 -0
- package/codeyam-cli/src/utils/__tests__/envFile.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js +500 -0
- package/codeyam-cli/src/utils/__tests__/handoffContext.test.js.map +1 -0
- 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__/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 +28 -1
- 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/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 +275 -21
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
- package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +27 -12
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +71 -0
- 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.server.js +31 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/envFile.js +90 -0
- package/codeyam-cli/src/utils/envFile.js.map +1 -0
- package/codeyam-cli/src/utils/handoffContext.js +257 -0
- package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +36 -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 +29 -3
- 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/scenariosManifest.js +8 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/techStackConfig.js +38 -0
- package/codeyam-cli/src/utils/techStackConfig.js.map +1 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js +85 -0
- package/codeyam-cli/src/utils/techStackConfig.test.js.map +1 -0
- 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__/buildPtyEnv.test.js +95 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.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 +67 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -0
- 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/build/client/assets/{CopyButton-CLe80MMu.js → CopyButton-DTBZZfSk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-Crt_KN_U.js → EntityItem-BxclONWq.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-CD7lGABo.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-CgTNOhnu.js → InlineSpinner-ByaELMbv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CKeQT5Ty.js → InteractivePreview-6WjVfhxX.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-D3s1MFkb.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CM5zg40N.js → LogViewer-C-9zQdXg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-Bs2_Oua4.js +36 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-C2PLkej3.js → ReportIssueModal-DQsceHVv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-DanvyBPb.js → SafeScreenshot-DThcm_9M.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DUMfcNVK.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-BA_Ry-rs.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-BAWd-Xjf.js → _index-DnOgyseQ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BOARiB-g.js → activity.(_tab)-DqM9hbNE.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-CHx25PAe.js → addon-web-links-C58dYPwR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-Bg3e7q4S.js → agent-transcripts-B8NCeOrm.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-database-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-github-verify-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-handoff-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-hosting-verify-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-CL-lMgHh.js → book-open-BFSIqZgO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-GmAjGS9-.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-DFcQkN5j.js → circle-check-DLPObLUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-C6iF61Xs.js → copy-DXEmO0TD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-4ImjHTVC.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-C8y4mmyv.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)-DOXe0Qx7.js +161 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-C6fEYHrh.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-pc-vc6wO.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DQM8E7L4.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CAoXLsQr.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-SuW9syRS.js → entry.client-j1Vi0bco.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{files-D-xGrg29.js → files-kuny2Q_s.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-Bq_fbXP5.js → git-DgCZPMie.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-Bp1l4hSv.js → index-BliGSSpl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-DE3jI_dv.js → index-SqjQKTdH.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-CWV9XZiG.js → index-vyrZD2g4.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{labs-B_IX45ih.js → labs-c3yLxSEp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-De-7qQ2u.js → loader-circle-D-q28GLF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-30c44d84.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-Cx2xEx7s.js → memory-CEWIUC4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{pause-CFxEKL1u.js → pause-BP6fitdh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-CLedrjXQ.js} +26 -13
- package/codeyam-cli/src/webserver/build/client/assets/{search-BdBb5aqc.js → search-BooqacKS.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{settings-DdE-Untf.js → settings-BM0nbryO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-DSCdE99u.js → simulations-ovy6FjRY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-CrplD4b1.js → terminal-DHemCJIs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-DqJ0j69l.js → triangle-alert-D87ekDl8.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DhXHbEjP.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-Cy5Qg_UR.js → useReportContext-jkCytuYz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-5HR2j9ZE.js → useToast-BgqkixU9.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-CuR5TvUx.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-CjLhfz6Z.js → index-D4MWAsqb.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-JObA4lXD.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-i8OXK4oL.js +765 -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 +27 -3
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/server.js +67 -0
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +103 -15
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
- package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
- package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
- 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 +4 -4
- 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/gitignore +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
- package/codeyam-cli/templates/seed-adapters/supabase.ts +271 -78
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +17 -2
- 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 +1 -0
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.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 +4 -4
- 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-D0LgAaSa.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BAdwhyCx.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-Gbk_i5Js.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DII1pg_z.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-cdf2c0a7.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BNd5hYuW.js +0 -2
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/init-BEqlbI84.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
- 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,6 +13,7 @@ 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";
|
|
@@ -21,7 +22,9 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
|
|
|
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";
|
|
27
|
+
import { updateHandoffProgress } from "../utils/handoffContext.js";
|
|
25
28
|
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
26
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
30
|
const __dirname = path.dirname(__filename);
|
|
@@ -187,6 +190,181 @@ function getProjectDimensions(root) {
|
|
|
187
190
|
return { defaultName: 'Desktop', names: [] };
|
|
188
191
|
}
|
|
189
192
|
}
|
|
193
|
+
function getTechStackContext(root) {
|
|
194
|
+
const state = readState(root);
|
|
195
|
+
const techStackId = state?.techStackId || '';
|
|
196
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
197
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
198
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
199
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
200
|
+
return {
|
|
201
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
202
|
+
isExpo,
|
|
203
|
+
isNextjs,
|
|
204
|
+
isChromeExt,
|
|
205
|
+
hasDatabase: isNextjs,
|
|
206
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
207
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
208
|
+
storageType: isExpo
|
|
209
|
+
? 'asyncStorage'
|
|
210
|
+
: isChromeExt
|
|
211
|
+
? 'chromeStorage'
|
|
212
|
+
: 'prisma',
|
|
213
|
+
routerImport: isExpo
|
|
214
|
+
? 'expo-router'
|
|
215
|
+
: isChromeExt
|
|
216
|
+
? 'react-router-dom'
|
|
217
|
+
: 'next/navigation',
|
|
218
|
+
componentPrimitives: isExpo
|
|
219
|
+
? '<View>, <Text>, <ScrollView>'
|
|
220
|
+
: '<div>, <span>, <h1>',
|
|
221
|
+
rawPrimitivesList: isExpo
|
|
222
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
223
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
224
|
+
patternsFile: isExpo
|
|
225
|
+
? 'MOBILE_SETUP.md'
|
|
226
|
+
: isChromeExt
|
|
227
|
+
? 'EXTENSION_SETUP.md'
|
|
228
|
+
: 'FEATURE_PATTERNS.md',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns a pre-populated tech stack for a given template ID.
|
|
233
|
+
* Written to .codeyam/config.json during `codeyam editor template`.
|
|
234
|
+
*/
|
|
235
|
+
function getTechStackForTemplate(templateId) {
|
|
236
|
+
switch (templateId) {
|
|
237
|
+
case 'nextjs-prisma-sqlite':
|
|
238
|
+
return {
|
|
239
|
+
languages: [
|
|
240
|
+
{
|
|
241
|
+
name: 'TypeScript',
|
|
242
|
+
url: 'https://typescriptlang.org',
|
|
243
|
+
description: 'Statically typed JavaScript superset',
|
|
244
|
+
version: '5',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
frameworks: [
|
|
248
|
+
{
|
|
249
|
+
name: 'Next.js',
|
|
250
|
+
url: 'https://nextjs.org',
|
|
251
|
+
description: 'Full-stack React framework with App Router, SSR, and API routes',
|
|
252
|
+
version: '15',
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'React',
|
|
256
|
+
url: 'https://react.dev',
|
|
257
|
+
description: 'Component-based UI library',
|
|
258
|
+
version: '19',
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
databases: [
|
|
262
|
+
{
|
|
263
|
+
name: 'SQLite',
|
|
264
|
+
url: 'https://sqlite.org',
|
|
265
|
+
description: 'Embedded relational database — zero config, upgradeable to hosted DB',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
libraries: [
|
|
269
|
+
{
|
|
270
|
+
name: 'Prisma',
|
|
271
|
+
url: 'https://prisma.io',
|
|
272
|
+
description: 'Type-safe database ORM and query builder',
|
|
273
|
+
version: '7',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'Tailwind CSS',
|
|
277
|
+
url: 'https://tailwindcss.com',
|
|
278
|
+
description: 'Utility-first CSS framework',
|
|
279
|
+
version: '4',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
case 'chrome-extension-react':
|
|
284
|
+
return {
|
|
285
|
+
languages: [
|
|
286
|
+
{
|
|
287
|
+
name: 'TypeScript',
|
|
288
|
+
url: 'https://typescriptlang.org',
|
|
289
|
+
description: 'Statically typed JavaScript superset',
|
|
290
|
+
version: '5',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
frameworks: [
|
|
294
|
+
{
|
|
295
|
+
name: 'React',
|
|
296
|
+
url: 'https://react.dev',
|
|
297
|
+
description: 'Component-based UI library for the popup and options pages',
|
|
298
|
+
version: '19',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'Vite',
|
|
302
|
+
url: 'https://vite.dev',
|
|
303
|
+
description: 'Fast build tool and dev server',
|
|
304
|
+
version: '6',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
libraries: [
|
|
308
|
+
{
|
|
309
|
+
name: 'Tailwind CSS',
|
|
310
|
+
url: 'https://tailwindcss.com',
|
|
311
|
+
description: 'Utility-first CSS framework',
|
|
312
|
+
version: '4',
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
infrastructure: [
|
|
316
|
+
{
|
|
317
|
+
name: 'Chrome Manifest V3',
|
|
318
|
+
url: 'https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3',
|
|
319
|
+
description: 'Extension platform with service workers, declarative APIs, and chrome.storage',
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
case 'expo-react-native':
|
|
324
|
+
return {
|
|
325
|
+
languages: [
|
|
326
|
+
{
|
|
327
|
+
name: 'TypeScript',
|
|
328
|
+
url: 'https://typescriptlang.org',
|
|
329
|
+
description: 'Statically typed JavaScript superset',
|
|
330
|
+
version: '5',
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
frameworks: [
|
|
334
|
+
{
|
|
335
|
+
name: 'Expo',
|
|
336
|
+
url: 'https://expo.dev',
|
|
337
|
+
description: 'React Native development platform with managed workflow and OTA updates',
|
|
338
|
+
version: 'SDK 54',
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'React Native',
|
|
342
|
+
url: 'https://reactnative.dev',
|
|
343
|
+
description: 'Cross-platform mobile UI framework',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'Expo Router',
|
|
347
|
+
url: 'https://docs.expo.dev/router/introduction/',
|
|
348
|
+
description: 'File-based navigation for React Native apps',
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
libraries: [
|
|
352
|
+
{
|
|
353
|
+
name: 'NativeWind',
|
|
354
|
+
url: 'https://www.nativewind.dev',
|
|
355
|
+
description: 'Tailwind CSS for React Native',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'AsyncStorage',
|
|
359
|
+
url: 'https://react-native-async-storage.github.io/async-storage/',
|
|
360
|
+
description: 'Persistent key-value storage for React Native',
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
default:
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
190
368
|
/**
|
|
191
369
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
192
370
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -210,6 +388,86 @@ function checkbox(text) {
|
|
|
210
388
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
211
389
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
212
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Print a checklist item for the handoff summary.
|
|
393
|
+
*/
|
|
394
|
+
function checkboxHandoff() {
|
|
395
|
+
checkbox('Update the handoff summary in `.codeyam/config.json` for the next session/AI: `codeyam editor handoff \'{"summary":"..."}\'`');
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Print the handoff context from a previous session or AI provider.
|
|
399
|
+
* Prefers the auto-generated handoff-context.md over the raw config summary.
|
|
400
|
+
*/
|
|
401
|
+
function printHandoffContext(root) {
|
|
402
|
+
try {
|
|
403
|
+
// Check for auto-generated handoff context (richer, includes progress timeline)
|
|
404
|
+
const handoffContextPath = path.join(root, '.codeyam', 'handoff-context.md');
|
|
405
|
+
if (fs.existsSync(handoffContextPath)) {
|
|
406
|
+
const content = fs.readFileSync(handoffContextPath, 'utf8');
|
|
407
|
+
console.log();
|
|
408
|
+
console.log(chalk.bold.magenta('━━━ PROVIDER HANDOFF CONTEXT ━━━'));
|
|
409
|
+
console.log(chalk.magenta(content));
|
|
410
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
411
|
+
console.log();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// Fall back to config.json handoff summary
|
|
415
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
416
|
+
if (!fs.existsSync(configPath))
|
|
417
|
+
return;
|
|
418
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
419
|
+
const handoff = config.handoff;
|
|
420
|
+
if (!handoff || !handoff.summary)
|
|
421
|
+
return;
|
|
422
|
+
console.log();
|
|
423
|
+
console.log(chalk.bold.magenta(`━━━ HANDOFF FROM ${String(handoff.lastProvider || 'unknown').toUpperCase()} (Step ${handoff.lastStep || 'unknown'}) ━━━`));
|
|
424
|
+
console.log(chalk.magenta(handoff.summary));
|
|
425
|
+
if (handoff.lastUpdated) {
|
|
426
|
+
console.log(chalk.dim(` Updated: ${handoff.lastUpdated}`));
|
|
427
|
+
}
|
|
428
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
429
|
+
console.log();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// Non-fatal
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
437
|
+
* Only prints creation instructions if the file doesn't exist yet.
|
|
438
|
+
* If it exists, reminds Claude to update it if data models changed.
|
|
439
|
+
*/
|
|
440
|
+
function printServiceRecordingReminder(port) {
|
|
441
|
+
console.log(chalk.bold('Third-party services:'));
|
|
442
|
+
checkbox('When integrating any external service (auth, email, payments, storage, telemetry), update the tech stack');
|
|
443
|
+
console.log(chalk.dim(' Include the service name, URL, description, and envKeys (environment variable names it requires).'));
|
|
444
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
445
|
+
console.log(chalk.dim(` -d '{"techStack": {"services": [{"name":"Stripe","url":"https://stripe.com","description":"Payments","envKeys":["STRIPE_SECRET_KEY","STRIPE_PUBLISHABLE_KEY"]}]}}'`));
|
|
446
|
+
console.log(chalk.dim(' This ensures the deploy roadmap tracks all services that need production credentials.'));
|
|
447
|
+
}
|
|
448
|
+
function printDataStructureInstructions() {
|
|
449
|
+
const root = getProjectRoot();
|
|
450
|
+
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
451
|
+
const exists = fs.existsSync(dsPath);
|
|
452
|
+
if (!exists) {
|
|
453
|
+
console.log(chalk.bold('Create the data structure config:'));
|
|
454
|
+
checkbox('Create `.codeyam/data-structure.json` describing all data sources');
|
|
455
|
+
console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
|
|
456
|
+
console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
|
|
457
|
+
console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
|
|
458
|
+
console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
|
|
459
|
+
console.log();
|
|
460
|
+
console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
|
|
461
|
+
console.log(chalk.dim(' "mock-api" for mocked external API responses'));
|
|
462
|
+
console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
|
|
463
|
+
console.log(chalk.dim(' Do NOT include types only used as component props.'));
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
|
|
467
|
+
console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
|
|
468
|
+
}
|
|
469
|
+
console.log();
|
|
470
|
+
}
|
|
213
471
|
/**
|
|
214
472
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
215
473
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -237,6 +495,10 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
237
495
|
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
238
496
|
}
|
|
239
497
|
console.log();
|
|
498
|
+
checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
|
|
499
|
+
console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
|
|
500
|
+
console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
|
|
501
|
+
console.log();
|
|
240
502
|
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
241
503
|
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
242
504
|
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
@@ -251,7 +513,12 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
251
513
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
252
514
|
}
|
|
253
515
|
else {
|
|
516
|
+
const appCtx = getTechStackContext(root);
|
|
254
517
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
518
|
+
if (appCtx.isExpo) {
|
|
519
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
520
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
521
|
+
}
|
|
255
522
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
256
523
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
257
524
|
}
|
|
@@ -299,6 +566,12 @@ function printExtractionPlanInstructions() {
|
|
|
299
566
|
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
300
567
|
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
301
568
|
console.log();
|
|
569
|
+
console.log(chalk.bold.red('THE DECOMPOSITION RULE: All code should do one thing.'));
|
|
570
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces to form a coordinated whole.'));
|
|
571
|
+
console.log(chalk.yellow(' Every opportunity to extract code into a sensible function, helper, or sub-component MUST be taken.'));
|
|
572
|
+
console.log(chalk.yellow(' Then extract AGAIN from the extracted code — keep going until each piece does one clearly defined thing.'));
|
|
573
|
+
console.log(chalk.yellow(' This applies to ALL code: backend routes, business logic, frontend components, utilities — everything.'));
|
|
574
|
+
console.log();
|
|
302
575
|
console.log(chalk.bold('Checklist:'));
|
|
303
576
|
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
304
577
|
checkbox('Read EVERY file used by this page/feature');
|
|
@@ -309,6 +582,12 @@ function printExtractionPlanInstructions() {
|
|
|
309
582
|
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
310
583
|
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
311
584
|
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
585
|
+
console.log(chalk.yellow(' Inline logic — backend AND frontend (MUST be extracted as named functions):'));
|
|
586
|
+
console.log(chalk.yellow(' conditionals/ternaries, computed/derived values, data transformations,'));
|
|
587
|
+
console.log(chalk.yellow(' string formatting/interpolation, array filtering/sorting/mapping callbacks,'));
|
|
588
|
+
console.log(chalk.yellow(' default value logic, null/undefined guards, date/number formatting,'));
|
|
589
|
+
console.log(chalk.yellow(' permission/role checks, status derivation (e.g. isOverdue(task)),'));
|
|
590
|
+
console.log(chalk.yellow(' request/response shaping, error message construction, config lookups'));
|
|
312
591
|
console.log();
|
|
313
592
|
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
314
593
|
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
@@ -319,6 +598,15 @@ function printExtractionPlanInstructions() {
|
|
|
319
598
|
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
320
599
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
321
600
|
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
601
|
+
console.log(chalk.yellow(' — How you will test it (key inputs/outputs and edge cases)'));
|
|
602
|
+
console.log(chalk.yellow(' — What logic inside it can be further extracted as a separate testable function'));
|
|
603
|
+
console.log();
|
|
604
|
+
console.log(chalk.bold.red('BEFORE FINALIZING THE PLAN:'));
|
|
605
|
+
console.log(chalk.yellow(' Re-read every file ONE MORE TIME. For each piece of code, ask:'));
|
|
606
|
+
console.log(chalk.yellow(' 1. Does this do more than one thing? → Split it into pieces that each do one thing.'));
|
|
607
|
+
console.log(chalk.yellow(' 2. Is there logic here that could be its own function with clear inputs/outputs? → Extract it.'));
|
|
608
|
+
console.log(chalk.yellow(' 3. Can the extracted code itself be broken down further? → Keep extracting.'));
|
|
609
|
+
console.log(chalk.yellow(' If your plan has fewer functions than components, you probably missed extractable logic.'));
|
|
322
610
|
console.log();
|
|
323
611
|
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
324
612
|
}
|
|
@@ -326,10 +614,18 @@ function printExtractionPlanInstructions() {
|
|
|
326
614
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
327
615
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
328
616
|
*/
|
|
329
|
-
function printComponentCaptureInstructions() {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
617
|
+
function printComponentCaptureInstructions(root) {
|
|
618
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
619
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
620
|
+
checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
621
|
+
if (isExpo) {
|
|
622
|
+
console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
|
|
623
|
+
console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
|
|
627
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
628
|
+
}
|
|
333
629
|
checkbox('For each visual component:');
|
|
334
630
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
335
631
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -340,23 +636,37 @@ function printComponentCaptureInstructions() {
|
|
|
340
636
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
341
637
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
342
638
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
343
|
-
|
|
344
|
-
|
|
639
|
+
if (isExpo) {
|
|
640
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
641
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
645
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
646
|
+
}
|
|
345
647
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
346
648
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
649
|
+
if (isExpo) {
|
|
650
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
651
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
655
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
656
|
+
}
|
|
657
|
+
console.log(chalk.dim(isExpo
|
|
658
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
659
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
660
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
351
661
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
352
662
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
353
663
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
354
664
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
355
665
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
356
666
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
357
|
-
console.log(chalk.dim(` "url":"/
|
|
667
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
358
668
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
359
|
-
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/
|
|
669
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
360
670
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
361
671
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
362
672
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -366,7 +676,7 @@ function printComponentCaptureInstructions() {
|
|
|
366
676
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
367
677
|
console.log();
|
|
368
678
|
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
369
|
-
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/
|
|
679
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
370
680
|
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
371
681
|
}
|
|
372
682
|
/**
|
|
@@ -419,7 +729,9 @@ function stopGate(current, opts) {
|
|
|
419
729
|
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
420
730
|
if (current >= 2) {
|
|
421
731
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
422
|
-
console.log(chalk.cyan('
|
|
732
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
733
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
734
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
423
735
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
424
736
|
console.log();
|
|
425
737
|
let taskTitle;
|
|
@@ -683,11 +995,17 @@ function printSetup(root) {
|
|
|
683
995
|
console.log();
|
|
684
996
|
// ── Design System ────────────────────────────────────────────────
|
|
685
997
|
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
686
|
-
console.log(chalk.dim(' Ask: "
|
|
998
|
+
console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
|
|
687
999
|
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
688
|
-
console.log(
|
|
1000
|
+
console.log();
|
|
1001
|
+
for (const ds of DESIGN_SYSTEMS) {
|
|
1002
|
+
console.log(chalk.yellow(` Option label: "${ds.name}"`) +
|
|
1003
|
+
chalk.dim(` — ${ds.description}`));
|
|
1004
|
+
console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
|
|
1005
|
+
}
|
|
1006
|
+
console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
|
|
689
1007
|
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
690
|
-
console.log(chalk.yellow(' Option
|
|
1008
|
+
console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
|
|
691
1009
|
console.log(chalk.dim(' → Skip'));
|
|
692
1010
|
console.log();
|
|
693
1011
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -708,8 +1026,8 @@ function printSetup(root) {
|
|
|
708
1026
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
709
1027
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
710
1028
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
711
|
-
console.log(chalk.dim('
|
|
712
|
-
console.log(chalk.dim('
|
|
1029
|
+
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.").'));
|
|
1030
|
+
console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
|
|
713
1031
|
console.log();
|
|
714
1032
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
715
1033
|
for (const stack of TECH_STACKS) {
|
|
@@ -742,7 +1060,7 @@ function printSetup(root) {
|
|
|
742
1060
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
743
1061
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
744
1062
|
console.log();
|
|
745
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
1063
|
+
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).'));
|
|
746
1064
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
747
1065
|
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}}'`));
|
|
748
1066
|
console.log();
|
|
@@ -901,6 +1219,15 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
901
1219
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
902
1220
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
903
1221
|
console.log();
|
|
1222
|
+
checkbox('Detect and set the project tech stack:');
|
|
1223
|
+
console.log(chalk.dim(' Scan package.json, framework configs (.env, .env.example, tsconfig.json, next.config.*, prisma/schema.prisma, etc.)'));
|
|
1224
|
+
console.log(chalk.dim(' to detect languages, frameworks, databases, services, libraries, and infrastructure.'));
|
|
1225
|
+
console.log(chalk.dim(' Each item MUST have name, url, and description. version and envKeys are optional.'));
|
|
1226
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
1227
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
1228
|
+
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":[]}}\''));
|
|
1229
|
+
console.log(chalk.dim(' Replace the example values with what you detect in this project. Omit empty categories.'));
|
|
1230
|
+
console.log();
|
|
904
1231
|
const designSystem = readDesignSystem(root);
|
|
905
1232
|
if (designSystem) {
|
|
906
1233
|
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
@@ -914,6 +1241,8 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
914
1241
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
915
1242
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
916
1243
|
console.log();
|
|
1244
|
+
checkboxHandoff();
|
|
1245
|
+
console.log();
|
|
917
1246
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
918
1247
|
console.log();
|
|
919
1248
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
@@ -951,33 +1280,80 @@ function printStep2(root, feature) {
|
|
|
951
1280
|
}
|
|
952
1281
|
console.log('Get the project ready to build.');
|
|
953
1282
|
console.log();
|
|
1283
|
+
const ctx = getTechStackContext(root);
|
|
954
1284
|
// If no project exists yet, include scaffolding instructions first
|
|
955
1285
|
if (!projectExists) {
|
|
956
1286
|
console.log(chalk.bold('Scaffold the project:'));
|
|
957
1287
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
958
|
-
|
|
1288
|
+
if (ctx.isExpo) {
|
|
1289
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1290
|
+
}
|
|
1291
|
+
else if (ctx.isChromeExt) {
|
|
1292
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1296
|
+
}
|
|
959
1297
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
960
1298
|
console.log();
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1299
|
+
if (ctx.isExpo) {
|
|
1300
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1301
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1302
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1303
|
+
console.log();
|
|
1304
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1305
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1306
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1307
|
+
console.log();
|
|
1308
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1309
|
+
console.log();
|
|
1310
|
+
checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
|
|
1311
|
+
console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
|
|
1312
|
+
console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
|
|
1313
|
+
port +
|
|
1314
|
+
'/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
|
|
1315
|
+
console.log();
|
|
1316
|
+
checkbox('Generate an app icon using `sharp`');
|
|
1317
|
+
console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
|
|
1318
|
+
console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
|
|
1319
|
+
console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
|
|
1320
|
+
console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
|
|
1321
|
+
console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
|
|
1322
|
+
console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
|
|
1323
|
+
console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
|
|
1324
|
+
console.log();
|
|
1325
|
+
}
|
|
1326
|
+
else if (ctx.isChromeExt) {
|
|
1327
|
+
// Chrome Extension: use chrome.storage
|
|
1328
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1329
|
+
console.log();
|
|
1330
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1331
|
+
console.log();
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
// Next.js: Prisma + database
|
|
1335
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1336
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1337
|
+
console.log();
|
|
1338
|
+
checkbox('Push schema and seed the database');
|
|
1339
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1340
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1341
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1342
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1343
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1344
|
+
console.log();
|
|
1345
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1346
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1347
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1348
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1349
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1350
|
+
console.log();
|
|
1351
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1352
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1353
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1354
|
+
console.log();
|
|
1355
|
+
}
|
|
1356
|
+
printDataStructureInstructions();
|
|
981
1357
|
}
|
|
982
1358
|
else {
|
|
983
1359
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -992,7 +1368,12 @@ function printStep2(root, feature) {
|
|
|
992
1368
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
993
1369
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
994
1370
|
console.log();
|
|
1371
|
+
printDataStructureInstructions();
|
|
995
1372
|
}
|
|
1373
|
+
console.log();
|
|
1374
|
+
printServiceRecordingReminder(port);
|
|
1375
|
+
console.log();
|
|
1376
|
+
checkboxHandoff();
|
|
996
1377
|
stopGate(2);
|
|
997
1378
|
}
|
|
998
1379
|
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
@@ -1017,26 +1398,42 @@ function printStep3(root, feature) {
|
|
|
1017
1398
|
if (isResuming) {
|
|
1018
1399
|
printResumptionHeader(3);
|
|
1019
1400
|
}
|
|
1401
|
+
const ctx = getTechStackContext(root);
|
|
1020
1402
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1021
1403
|
console.log();
|
|
1022
1404
|
console.log(chalk.bold('Checklist:'));
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1405
|
+
if (ctx.isExpo) {
|
|
1406
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1407
|
+
if (!projectExists) {
|
|
1408
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1409
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1410
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1411
|
+
console.log();
|
|
1412
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1413
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1414
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1415
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1420
|
+
if (!projectExists) {
|
|
1421
|
+
checkbox('Seed the database with demo data');
|
|
1422
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1423
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1424
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1425
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1426
|
+
console.log();
|
|
1427
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1428
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1429
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1430
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1431
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1432
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1433
|
+
}
|
|
1037
1434
|
}
|
|
1038
1435
|
checkbox('Verify the dev server shows the changes');
|
|
1039
|
-
checkbox(
|
|
1436
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1040
1437
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1041
1438
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1042
1439
|
console.log();
|
|
@@ -1053,16 +1450,33 @@ function printStep3(root, feature) {
|
|
|
1053
1450
|
console.log();
|
|
1054
1451
|
console.log(designSystem);
|
|
1055
1452
|
console.log();
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1453
|
+
if (ctx.isExpo) {
|
|
1454
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1455
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1456
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1457
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1458
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1459
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1460
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1461
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1462
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1463
|
+
checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
|
|
1464
|
+
console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
|
|
1465
|
+
console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
|
|
1466
|
+
console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
|
|
1467
|
+
}
|
|
1468
|
+
else {
|
|
1469
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1470
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1471
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1472
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1473
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1474
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1475
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1476
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1477
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1478
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1479
|
+
}
|
|
1066
1480
|
}
|
|
1067
1481
|
console.log();
|
|
1068
1482
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1076,6 +1490,9 @@ function printStep3(root, feature) {
|
|
|
1076
1490
|
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1077
1491
|
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1078
1492
|
console.log();
|
|
1493
|
+
printServiceRecordingReminder(port);
|
|
1494
|
+
console.log();
|
|
1495
|
+
checkboxHandoff();
|
|
1079
1496
|
stopGate(3);
|
|
1080
1497
|
}
|
|
1081
1498
|
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
@@ -1090,6 +1507,8 @@ function printStep4(root, feature) {
|
|
|
1090
1507
|
label: STEP_LABELS[4],
|
|
1091
1508
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1092
1509
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1510
|
+
appFormats: prevState?.appFormats,
|
|
1511
|
+
techStackId: prevState?.techStackId,
|
|
1093
1512
|
});
|
|
1094
1513
|
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1095
1514
|
stepHeader(4, 'Verify Prototype', feature);
|
|
@@ -1099,14 +1518,13 @@ function printStep4(root, feature) {
|
|
|
1099
1518
|
console.log('Verify everything works before presenting the prototype.');
|
|
1100
1519
|
console.log();
|
|
1101
1520
|
console.log(chalk.bold('Verify the dev server:'));
|
|
1102
|
-
console.log(chalk.dim(
|
|
1103
|
-
console.log(chalk.dim(
|
|
1104
|
-
console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
|
|
1521
|
+
console.log(chalk.dim(' # Verify pages and API routes load:'));
|
|
1522
|
+
console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
|
|
1105
1523
|
console.log();
|
|
1106
1524
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1107
1525
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
1108
|
-
checkbox('Verify
|
|
1109
|
-
|
|
1526
|
+
checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1527
|
+
console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
|
|
1110
1528
|
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1111
1529
|
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
1112
1530
|
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
@@ -1117,6 +1535,14 @@ function printStep4(root, feature) {
|
|
|
1117
1535
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1118
1536
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1119
1537
|
console.log();
|
|
1538
|
+
const ctx4 = getTechStackContext(root);
|
|
1539
|
+
if (ctx4.isExpo) {
|
|
1540
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1541
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1542
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1543
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1544
|
+
console.log();
|
|
1545
|
+
}
|
|
1120
1546
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1121
1547
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1122
1548
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1124,6 +1550,8 @@ function printStep4(root, feature) {
|
|
|
1124
1550
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1125
1551
|
console.log();
|
|
1126
1552
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1553
|
+
console.log();
|
|
1554
|
+
checkboxHandoff();
|
|
1127
1555
|
stopGate(4);
|
|
1128
1556
|
}
|
|
1129
1557
|
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
@@ -1139,6 +1567,8 @@ function printStep5(root, feature) {
|
|
|
1139
1567
|
label: STEP_LABELS[5],
|
|
1140
1568
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1141
1569
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1570
|
+
appFormats: prevState?.appFormats,
|
|
1571
|
+
techStackId: prevState?.techStackId,
|
|
1142
1572
|
});
|
|
1143
1573
|
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1144
1574
|
stepHeader(5, 'Confirm', feature);
|
|
@@ -1149,7 +1579,7 @@ function printStep5(root, feature) {
|
|
|
1149
1579
|
console.log();
|
|
1150
1580
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
1151
1581
|
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
1152
|
-
checkbox('Verify
|
|
1582
|
+
checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1153
1583
|
console.log();
|
|
1154
1584
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
1155
1585
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
@@ -1185,6 +1615,8 @@ function printStep5(root, feature) {
|
|
|
1185
1615
|
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1186
1616
|
console.log();
|
|
1187
1617
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1618
|
+
console.log();
|
|
1619
|
+
checkboxHandoff();
|
|
1188
1620
|
stopGate(5, { confirm: true });
|
|
1189
1621
|
}
|
|
1190
1622
|
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
@@ -1198,6 +1630,8 @@ function printStep6(root, feature) {
|
|
|
1198
1630
|
label: STEP_LABELS[6],
|
|
1199
1631
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1200
1632
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1633
|
+
appFormats: prevState?.appFormats,
|
|
1634
|
+
techStackId: prevState?.techStackId,
|
|
1201
1635
|
});
|
|
1202
1636
|
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1203
1637
|
stepHeader(6, 'Deconstruct', feature);
|
|
@@ -1208,6 +1642,8 @@ function printStep6(root, feature) {
|
|
|
1208
1642
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1209
1643
|
console.log();
|
|
1210
1644
|
printExtractionPlanInstructions();
|
|
1645
|
+
console.log();
|
|
1646
|
+
checkboxHandoff();
|
|
1211
1647
|
stopGate(6);
|
|
1212
1648
|
}
|
|
1213
1649
|
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
@@ -1223,12 +1659,15 @@ function printStep7(root, feature) {
|
|
|
1223
1659
|
label: STEP_LABELS[7],
|
|
1224
1660
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1225
1661
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1662
|
+
appFormats: prevState?.appFormats,
|
|
1663
|
+
techStackId: prevState?.techStackId,
|
|
1226
1664
|
});
|
|
1227
1665
|
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1228
1666
|
stepHeader(7, 'Extract', feature);
|
|
1229
1667
|
if (isResuming) {
|
|
1230
1668
|
printResumptionHeader(7);
|
|
1231
1669
|
}
|
|
1670
|
+
const ctx = getTechStackContext(root);
|
|
1232
1671
|
console.log('Execute your extraction plan from step 6.');
|
|
1233
1672
|
console.log();
|
|
1234
1673
|
console.log(chalk.bold('Components:'));
|
|
@@ -1241,19 +1680,45 @@ function printStep7(root, feature) {
|
|
|
1241
1680
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1242
1681
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1243
1682
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1683
|
+
console.log(chalk.dim(' EVERY conditional branch in the function MUST have a test that exercises it'));
|
|
1684
|
+
console.log(chalk.dim(' EVERY return path MUST be tested — if a function can return 3 different things, write 3+ tests'));
|
|
1685
|
+
console.log(chalk.dim(' Boundary values: 0, 1, -1, empty string, empty array, undefined, null'));
|
|
1244
1686
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1245
|
-
|
|
1687
|
+
console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
|
|
1688
|
+
if (ctx.isExpo) {
|
|
1689
|
+
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`');
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1693
|
+
}
|
|
1246
1694
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1247
1695
|
console.log();
|
|
1248
1696
|
console.log(chalk.bold('Recursive pass:'));
|
|
1249
1697
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1250
1698
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1251
|
-
console.log(chalk.yellow(
|
|
1699
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1252
1700
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1253
1701
|
console.log();
|
|
1702
|
+
console.log(chalk.bold('Decomposition pass (backend AND frontend):'));
|
|
1703
|
+
checkbox('Re-read EVERY file you created or modified. For each, extract a function/helper/sub-component for:');
|
|
1704
|
+
console.log(chalk.yellow(' — Conditionals (e.g. `isOverdue ? "red" : "green"` → `getStatusColor(date)`)'));
|
|
1705
|
+
console.log(chalk.yellow(' — Computed values (e.g. `items.filter(...).length` → `countActiveItems(items)`)'));
|
|
1706
|
+
console.log(chalk.yellow(' — Formatting (e.g. `${price.toFixed(2)}` → `formatPrice(price)`)'));
|
|
1707
|
+
console.log(chalk.yellow(' — Data transforms, validation, request/response shaping'));
|
|
1708
|
+
console.log(chalk.yellow(' — Anything doing more than one thing → split until each piece does one clearly defined thing'));
|
|
1709
|
+
checkbox('Then look at what you just extracted — can IT be broken down further? Keep going.');
|
|
1710
|
+
checkbox('Write tests for every extracted function (same TDD: failing tests FIRST)');
|
|
1711
|
+
console.log();
|
|
1712
|
+
console.log(chalk.bold.red('TEST QUALITY SELF-CHECK (before proceeding):'));
|
|
1713
|
+
checkbox('For EACH test file: count the test cases. Fewer than 3 tests for any function is a red flag.');
|
|
1714
|
+
checkbox('For EACH tested function: read the function, count the conditional branches. Every branch MUST have a test.');
|
|
1715
|
+
checkbox('For EACH tested function: verify you test at least one error/edge case, not just happy paths.');
|
|
1716
|
+
console.log(chalk.yellow(' If a function has an if/else, switch, or ternary — each path needs its own test case.'));
|
|
1717
|
+
console.log(chalk.yellow(' If a function handles null/undefined/empty — test those inputs explicitly.'));
|
|
1718
|
+
console.log();
|
|
1254
1719
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1255
1720
|
checkbox('Run all tests and verify they pass');
|
|
1256
|
-
checkbox(
|
|
1721
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1257
1722
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1258
1723
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1259
1724
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1261,6 +1726,8 @@ function printStep7(root, feature) {
|
|
|
1261
1726
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1262
1727
|
console.log();
|
|
1263
1728
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1729
|
+
console.log();
|
|
1730
|
+
checkboxHandoff();
|
|
1264
1731
|
stopGate(7);
|
|
1265
1732
|
}
|
|
1266
1733
|
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
@@ -1274,6 +1741,8 @@ function printStep8(root, feature) {
|
|
|
1274
1741
|
label: STEP_LABELS[8],
|
|
1275
1742
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1276
1743
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1744
|
+
appFormats: prevState?.appFormats,
|
|
1745
|
+
techStackId: prevState?.techStackId,
|
|
1277
1746
|
});
|
|
1278
1747
|
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1279
1748
|
stepHeader(8, 'Glossary', feature);
|
|
@@ -1300,6 +1769,8 @@ function printStep9(root, feature) {
|
|
|
1300
1769
|
label: STEP_LABELS[9],
|
|
1301
1770
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1302
1771
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1772
|
+
appFormats: prevState?.appFormats,
|
|
1773
|
+
techStackId: prevState?.techStackId,
|
|
1303
1774
|
});
|
|
1304
1775
|
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1305
1776
|
stepHeader(9, 'Analyze and Verify', feature);
|
|
@@ -1314,19 +1785,35 @@ function printStep9(root, feature) {
|
|
|
1314
1785
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1315
1786
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1316
1787
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1317
|
-
|
|
1788
|
+
const ctx9 = getTechStackContext(root);
|
|
1789
|
+
printComponentCaptureInstructions(root);
|
|
1318
1790
|
console.log();
|
|
1319
1791
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1320
1792
|
checkbox('Run ALL test files created in step 7');
|
|
1321
|
-
console.log(chalk.dim(
|
|
1793
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1322
1794
|
checkbox('Verify every test passes');
|
|
1323
1795
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1324
1796
|
console.log();
|
|
1325
1797
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1326
1798
|
console.log();
|
|
1799
|
+
console.log(chalk.bold.red('DECOMPOSITION & COVERAGE REVIEW (before running audit):'));
|
|
1800
|
+
checkbox('Re-read every file. Extract any logic that could be its own function/helper/sub-component.');
|
|
1801
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces.'));
|
|
1802
|
+
console.log(chalk.yellow(' Keep extracting until each piece does one clearly defined thing.'));
|
|
1803
|
+
console.log(chalk.yellow(' For each extraction: write failing test first, then extract, then make test pass.'));
|
|
1804
|
+
checkbox('Open each test file and verify: ≥3 test cases per function, happy path + edge case + error case');
|
|
1805
|
+
checkbox('If any function has fewer tests than conditional branches, add the missing tests NOW');
|
|
1806
|
+
checkbox('Update `.codeyam/glossary.json` with any new functions before running audit');
|
|
1807
|
+
console.log();
|
|
1327
1808
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1328
1809
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1329
|
-
console.log(chalk.dim('
|
|
1810
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1811
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1812
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1813
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1814
|
+
console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
|
|
1815
|
+
console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
|
|
1816
|
+
console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
|
|
1330
1817
|
console.log();
|
|
1331
1818
|
stopGate(9);
|
|
1332
1819
|
}
|
|
@@ -1343,6 +1830,8 @@ function printStep10(root, feature) {
|
|
|
1343
1830
|
label: STEP_LABELS[10],
|
|
1344
1831
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1345
1832
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1833
|
+
appFormats: prevState?.appFormats,
|
|
1834
|
+
techStackId: prevState?.techStackId,
|
|
1346
1835
|
});
|
|
1347
1836
|
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1348
1837
|
stepHeader(10, 'App Scenarios', feature);
|
|
@@ -1407,6 +1896,8 @@ function printStep11(root, feature) {
|
|
|
1407
1896
|
label: STEP_LABELS[11],
|
|
1408
1897
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1409
1898
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1899
|
+
appFormats: prevState?.appFormats,
|
|
1900
|
+
techStackId: prevState?.techStackId,
|
|
1410
1901
|
});
|
|
1411
1902
|
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1412
1903
|
stepHeader(11, 'User Scenarios', feature);
|
|
@@ -1453,6 +1944,8 @@ function printStep12(root, feature) {
|
|
|
1453
1944
|
label: STEP_LABELS[12],
|
|
1454
1945
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1455
1946
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1947
|
+
appFormats: prevState?.appFormats,
|
|
1948
|
+
techStackId: prevState?.techStackId,
|
|
1456
1949
|
});
|
|
1457
1950
|
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1458
1951
|
stepHeader(12, 'Verify', feature);
|
|
@@ -1499,6 +1992,8 @@ function printStep13(root, feature) {
|
|
|
1499
1992
|
label: STEP_LABELS[13],
|
|
1500
1993
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1501
1994
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1995
|
+
appFormats: prevState?.appFormats,
|
|
1996
|
+
techStackId: prevState?.techStackId,
|
|
1502
1997
|
});
|
|
1503
1998
|
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1504
1999
|
stepHeader(13, 'Journal', feature);
|
|
@@ -1532,6 +2027,8 @@ function printStep14(root, feature) {
|
|
|
1532
2027
|
label: STEP_LABELS[14],
|
|
1533
2028
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1534
2029
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2030
|
+
appFormats: prevState?.appFormats,
|
|
2031
|
+
techStackId: prevState?.techStackId,
|
|
1535
2032
|
});
|
|
1536
2033
|
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
1537
2034
|
stepHeader(14, 'Review', feature);
|
|
@@ -1551,6 +2048,8 @@ function printStep14(root, feature) {
|
|
|
1551
2048
|
checkbox('Fix or remove any broken images before continuing');
|
|
1552
2049
|
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1553
2050
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
2051
|
+
checkbox('Verify tech stack is up to date: check for any new service imports (e.g. Stripe, Resend, Supabase) and ensure they appear in the tech stack with `envKeys`');
|
|
2052
|
+
console.log(chalk.dim(` Update via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"techStack": {...}}'`));
|
|
1554
2053
|
checkbox('Do not proceed until all checks pass');
|
|
1555
2054
|
stopGate(14);
|
|
1556
2055
|
}
|
|
@@ -1565,6 +2064,8 @@ function printStep15(root, feature) {
|
|
|
1565
2064
|
label: STEP_LABELS[15],
|
|
1566
2065
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1567
2066
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2067
|
+
appFormats: prevState?.appFormats,
|
|
2068
|
+
techStackId: prevState?.techStackId,
|
|
1568
2069
|
});
|
|
1569
2070
|
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
1570
2071
|
stepHeader(15, 'Present', feature);
|
|
@@ -1793,7 +2294,7 @@ function printMigrateStep3(root) {
|
|
|
1793
2294
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1794
2295
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1795
2296
|
console.log();
|
|
1796
|
-
printComponentCaptureInstructions();
|
|
2297
|
+
printComponentCaptureInstructions(root);
|
|
1797
2298
|
console.log();
|
|
1798
2299
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1799
2300
|
}
|
|
@@ -1928,7 +2429,7 @@ function printMigrateStep8(root) {
|
|
|
1928
2429
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1929
2430
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1930
2431
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1931
|
-
checkbox('Find all scenarios that use the real page route (not /
|
|
2432
|
+
checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
|
|
1932
2433
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1933
2434
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1934
2435
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1936,10 +2437,10 @@ function printMigrateStep8(root) {
|
|
|
1936
2437
|
console.log();
|
|
1937
2438
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1938
2439
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1939
|
-
printComponentCaptureInstructions();
|
|
2440
|
+
printComponentCaptureInstructions(root);
|
|
1940
2441
|
console.log();
|
|
1941
2442
|
console.log(chalk.bold('Verify:'));
|
|
1942
|
-
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
2443
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
1943
2444
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1944
2445
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1945
2446
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -2214,6 +2715,8 @@ function printStep16(root, feature) {
|
|
|
2214
2715
|
label: STEP_LABELS[16],
|
|
2215
2716
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2216
2717
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2718
|
+
appFormats: prevState?.appFormats,
|
|
2719
|
+
techStackId: prevState?.techStackId,
|
|
2217
2720
|
});
|
|
2218
2721
|
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2219
2722
|
stepHeader(16, 'Commit', feature);
|
|
@@ -2240,6 +2743,8 @@ function printStep17(root, feature) {
|
|
|
2240
2743
|
label: STEP_LABELS[17],
|
|
2241
2744
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2242
2745
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2746
|
+
appFormats: prevState?.appFormats,
|
|
2747
|
+
techStackId: prevState?.techStackId,
|
|
2243
2748
|
});
|
|
2244
2749
|
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2245
2750
|
stepHeader(17, 'Finalize', feature);
|
|
@@ -2265,6 +2770,8 @@ function printStep18(root, feature) {
|
|
|
2265
2770
|
label: STEP_LABELS[18],
|
|
2266
2771
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2267
2772
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2773
|
+
appFormats: prevState?.appFormats,
|
|
2774
|
+
techStackId: prevState?.techStackId,
|
|
2268
2775
|
});
|
|
2269
2776
|
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2270
2777
|
stepHeader(18, 'Push', feature);
|
|
@@ -2282,11 +2789,11 @@ function printStep18(root, feature) {
|
|
|
2282
2789
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2283
2790
|
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2284
2791
|
console.log();
|
|
2285
|
-
|
|
2286
|
-
console.log(chalk.yellow('
|
|
2792
|
+
const port = getServerPort();
|
|
2793
|
+
console.log(chalk.bold.yellow('IMPORTANT: After the push succeeds (or the user skips it), the feature is DONE.'));
|
|
2794
|
+
console.log(chalk.yellow(` Signal the UI by running: curl -s -X POST http://localhost:${port}/api/editor-feature-complete`));
|
|
2795
|
+
console.log(chalk.yellow(' Then STOP and wait. Do NOT ask the user what to build next — the UI handles the transition.'));
|
|
2287
2796
|
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2288
|
-
console.log(chalk.yellow(' Ask the user what they want to build. Once they tell you, run `codeyam editor 1` yourself.'));
|
|
2289
|
-
console.log(chalk.yellow(' NEVER tell the user to run `codeyam editor` commands — these are internal. Just ask what to build.'));
|
|
2290
2797
|
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2291
2798
|
stopGate(18, { confirm: true });
|
|
2292
2799
|
}
|
|
@@ -2317,10 +2824,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2317
2824
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2318
2825
|
}
|
|
2319
2826
|
catch {
|
|
2827
|
+
if (options.silent)
|
|
2828
|
+
return;
|
|
2320
2829
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2321
2830
|
process.exit(1);
|
|
2322
2831
|
}
|
|
2323
2832
|
if (glossaryEntries.length === 0) {
|
|
2833
|
+
if (options.silent)
|
|
2834
|
+
return;
|
|
2324
2835
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2325
2836
|
process.exit(1);
|
|
2326
2837
|
}
|
|
@@ -2362,25 +2873,137 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2362
2873
|
// Non-fatal — scenario file paths just won't be included
|
|
2363
2874
|
}
|
|
2364
2875
|
const progress = new ProgressReporter();
|
|
2365
|
-
//
|
|
2876
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2877
|
+
// ~120 files when only 2 are incomplete.
|
|
2878
|
+
let targetFilePaths = filePaths;
|
|
2879
|
+
try {
|
|
2880
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2881
|
+
const db = getDatabase();
|
|
2882
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2883
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2884
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2885
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2886
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2887
|
+
if (!options.silent) {
|
|
2888
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2889
|
+
}
|
|
2890
|
+
targetFilePaths = incomplete;
|
|
2891
|
+
}
|
|
2892
|
+
else if (incomplete.length === 0) {
|
|
2893
|
+
if (!options.silent) {
|
|
2894
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2895
|
+
}
|
|
2896
|
+
// Still need to run the import graph + backfill below
|
|
2897
|
+
targetFilePaths = [];
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
catch {
|
|
2901
|
+
// Non-fatal — fall back to analyzing all files
|
|
2902
|
+
}
|
|
2903
|
+
// If specific file paths were requested, scope analysis to only those files.
|
|
2904
|
+
// The full filePaths set is still used for the import graph below.
|
|
2905
|
+
if (options.filePaths && options.filePaths.length > 0) {
|
|
2906
|
+
const requested = new Set(options.filePaths);
|
|
2907
|
+
targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
|
|
2908
|
+
if (targetFilePaths.length === 0 && !options.silent) {
|
|
2909
|
+
console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2366
2913
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2367
2914
|
// The analyzer will discover and create them from file paths.
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2915
|
+
if (targetFilePaths.length > 0) {
|
|
2916
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2917
|
+
try {
|
|
2918
|
+
await runAnalysisForEntities({
|
|
2919
|
+
projectRoot: root,
|
|
2920
|
+
filePaths: targetFilePaths,
|
|
2921
|
+
progress,
|
|
2922
|
+
onlyDataStructure: true,
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
catch (err) {
|
|
2926
|
+
progress.fail('Analysis failed');
|
|
2927
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2928
|
+
if (options.silent) {
|
|
2929
|
+
// Internal caller — don't kill the process, let the caller handle it
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2933
|
+
process.exit(1);
|
|
2934
|
+
}
|
|
2935
|
+
progress.succeed('Analysis complete');
|
|
2376
2936
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2937
|
+
// Surface analysis errors if any occurred
|
|
2938
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2939
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2940
|
+
if (!options.silent) {
|
|
2941
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2942
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2943
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2944
|
+
}
|
|
2945
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2946
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2947
|
+
// with the files we tried to analyze.
|
|
2948
|
+
try {
|
|
2949
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2950
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2951
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2952
|
+
// Parse error messages to identify failed file paths.
|
|
2953
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2954
|
+
// or more generic "CodeYam Error: <message>"
|
|
2955
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2956
|
+
const failedEntityNames = new Set();
|
|
2957
|
+
let match;
|
|
2958
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2959
|
+
const name = match[1] || match[2];
|
|
2960
|
+
if (name)
|
|
2961
|
+
failedEntityNames.add(name);
|
|
2962
|
+
}
|
|
2963
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2964
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2965
|
+
let glossary = [];
|
|
2966
|
+
try {
|
|
2967
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2968
|
+
}
|
|
2969
|
+
catch {
|
|
2970
|
+
// Non-fatal
|
|
2971
|
+
}
|
|
2972
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2973
|
+
const now = new Date().toISOString();
|
|
2974
|
+
const updatedFailures = { ...existingFailures };
|
|
2975
|
+
if (failedEntityNames.size > 0) {
|
|
2976
|
+
for (const name of failedEntityNames) {
|
|
2977
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2978
|
+
if (entry) {
|
|
2979
|
+
updatedFailures[entry.filePath] = {
|
|
2980
|
+
entityName: name,
|
|
2981
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2982
|
+
failedAt: now,
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
// When we can't parse specific entity names from the error, DON'T mark
|
|
2988
|
+
// all target files as failed. The error may be non-fatal (e.g., a cache
|
|
2989
|
+
// miss logged as "CodeYam Error") and blanket-marking every file as
|
|
2990
|
+
// permanently failed blocks future audits from resolving them.
|
|
2991
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2992
|
+
}
|
|
2993
|
+
catch {
|
|
2994
|
+
// Non-fatal — failure tracking is best-effort
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
else {
|
|
2998
|
+
// No errors — clear any stale failure tracking
|
|
2999
|
+
try {
|
|
3000
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
3001
|
+
writeAnalysisFailures(root, {});
|
|
3002
|
+
}
|
|
3003
|
+
catch {
|
|
3004
|
+
// Non-fatal
|
|
3005
|
+
}
|
|
2382
3006
|
}
|
|
2383
|
-
progress.succeed('Analysis complete');
|
|
2384
3007
|
// Load entities WITH metadata from database
|
|
2385
3008
|
progress.start('Loading entity metadata...');
|
|
2386
3009
|
await initializeEnvironment();
|
|
@@ -2563,43 +3186,98 @@ async function handleDelete(scenarioId) {
|
|
|
2563
3186
|
* Creates isolation route directories and the layout guard file.
|
|
2564
3187
|
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2565
3188
|
*/
|
|
3189
|
+
function handleDesignSystem(designSystemId) {
|
|
3190
|
+
const root = process.cwd();
|
|
3191
|
+
const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
|
|
3192
|
+
if (!ds) {
|
|
3193
|
+
console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
|
|
3194
|
+
process.exit(1);
|
|
3195
|
+
}
|
|
3196
|
+
const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
|
|
3197
|
+
if (!fs.existsSync(srcPath)) {
|
|
3198
|
+
console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
|
|
3199
|
+
process.exit(1);
|
|
3200
|
+
}
|
|
3201
|
+
const destDir = path.join(root, '.codeyam');
|
|
3202
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
3203
|
+
const destPath = path.join(destDir, 'design-system.md');
|
|
3204
|
+
fs.copyFileSync(srcPath, destPath);
|
|
3205
|
+
console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
|
|
3206
|
+
}
|
|
2566
3207
|
function handleIsolate(componentNames) {
|
|
2567
3208
|
const root = process.cwd();
|
|
3209
|
+
const ctx = getTechStackContext(root);
|
|
2568
3210
|
if (componentNames.length === 0) {
|
|
2569
3211
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2570
3212
|
process.exit(1);
|
|
2571
3213
|
}
|
|
2572
3214
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2573
|
-
// Create
|
|
2574
|
-
|
|
3215
|
+
// Create the framework-appropriate layout guard if missing.
|
|
3216
|
+
// Clean up wrong-framework layout from a previous CLI version.
|
|
3217
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
3218
|
+
const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
|
|
3219
|
+
if (fs.existsSync(wrongLayoutPath)) {
|
|
3220
|
+
fs.unlinkSync(wrongLayoutPath);
|
|
3221
|
+
}
|
|
2575
3222
|
if (!fs.existsSync(layoutPath)) {
|
|
2576
3223
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
3224
|
+
if (ctx.isExpo) {
|
|
3225
|
+
fs.writeFileSync(layoutPath, [
|
|
3226
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
3227
|
+
'',
|
|
3228
|
+
'export default function CaptureLayout() {',
|
|
3229
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
3230
|
+
' return <Slot />;',
|
|
3231
|
+
'}',
|
|
3232
|
+
'',
|
|
3233
|
+
].join('\n'), 'utf8');
|
|
3234
|
+
}
|
|
3235
|
+
else {
|
|
3236
|
+
fs.writeFileSync(layoutPath, [
|
|
3237
|
+
'import { notFound } from "next/navigation";',
|
|
3238
|
+
'',
|
|
3239
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
3240
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
3241
|
+
' return <>{children}</>;',
|
|
3242
|
+
'}',
|
|
3243
|
+
'',
|
|
3244
|
+
].join('\n'), 'utf8');
|
|
3245
|
+
}
|
|
3246
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
3247
|
+
}
|
|
3248
|
+
// Create isolation route for each component.
|
|
3249
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2589
3250
|
const created = [];
|
|
2590
3251
|
const existed = [];
|
|
2591
3252
|
for (const name of componentNames) {
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3253
|
+
if (ctx.isExpo) {
|
|
3254
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
3255
|
+
if (fs.existsSync(filePath)) {
|
|
3256
|
+
existed.push(name);
|
|
3257
|
+
}
|
|
3258
|
+
else {
|
|
3259
|
+
created.push(name);
|
|
3260
|
+
}
|
|
2595
3261
|
}
|
|
2596
3262
|
else {
|
|
2597
|
-
|
|
2598
|
-
|
|
3263
|
+
const dir = path.join(isolateDir, name);
|
|
3264
|
+
if (fs.existsSync(dir)) {
|
|
3265
|
+
existed.push(name);
|
|
3266
|
+
}
|
|
3267
|
+
else {
|
|
3268
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3269
|
+
created.push(name);
|
|
3270
|
+
}
|
|
2599
3271
|
}
|
|
2600
3272
|
}
|
|
2601
3273
|
if (created.length > 0) {
|
|
2602
|
-
|
|
3274
|
+
if (ctx.isExpo) {
|
|
3275
|
+
console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
|
|
3276
|
+
console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
|
|
3277
|
+
}
|
|
3278
|
+
else {
|
|
3279
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
3280
|
+
}
|
|
2603
3281
|
}
|
|
2604
3282
|
if (existed.length > 0) {
|
|
2605
3283
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2653,6 +3331,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2653
3331
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2654
3332
|
if (data.sessionsNotified != null)
|
|
2655
3333
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
3334
|
+
// Surface the HTTP health check from the dev server
|
|
3335
|
+
if (data.preview) {
|
|
3336
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
3337
|
+
if (data.preview.error)
|
|
3338
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3339
|
+
}
|
|
2656
3340
|
return parts.join(' ');
|
|
2657
3341
|
}
|
|
2658
3342
|
case 'dev-server': {
|
|
@@ -2702,10 +3386,82 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2702
3386
|
}
|
|
2703
3387
|
return parts.join(' ');
|
|
2704
3388
|
}
|
|
3389
|
+
case 'verify-routes': {
|
|
3390
|
+
const lines = [];
|
|
3391
|
+
lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
|
|
3392
|
+
for (const [route, info] of Object.entries(data.routes || {})) {
|
|
3393
|
+
const r = info;
|
|
3394
|
+
if (r.ok) {
|
|
3395
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
|
|
3396
|
+
}
|
|
3397
|
+
else {
|
|
3398
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
for (const [route, info] of Object.entries(data.apiRoutes || {})) {
|
|
3402
|
+
const r = info;
|
|
3403
|
+
if (r.ok && r.isJSON) {
|
|
3404
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
|
|
3405
|
+
}
|
|
3406
|
+
else if (r.ok) {
|
|
3407
|
+
lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
|
|
3408
|
+
}
|
|
3409
|
+
else {
|
|
3410
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
lines.push('');
|
|
3414
|
+
lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
|
|
3415
|
+
return lines.join('\n');
|
|
3416
|
+
}
|
|
2705
3417
|
default:
|
|
2706
3418
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2707
3419
|
}
|
|
2708
3420
|
}
|
|
3421
|
+
/**
|
|
3422
|
+
* `codeyam editor handoff '{"summary":"..."}'`
|
|
3423
|
+
*
|
|
3424
|
+
* Update the handoff summary in .codeyam/config.json for other AI providers.
|
|
3425
|
+
*/
|
|
3426
|
+
function handleHandoff(jsonArg) {
|
|
3427
|
+
if (!jsonArg) {
|
|
3428
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3429
|
+
console.error(chalk.dim(' Usage: codeyam editor handoff \'{"summary":"Built the X component..."}\''));
|
|
3430
|
+
process.exit(1);
|
|
3431
|
+
}
|
|
3432
|
+
let summary;
|
|
3433
|
+
try {
|
|
3434
|
+
const parsed = JSON.parse(jsonArg);
|
|
3435
|
+
summary = parsed.summary;
|
|
3436
|
+
}
|
|
3437
|
+
catch {
|
|
3438
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
3439
|
+
process.exit(1);
|
|
3440
|
+
}
|
|
3441
|
+
if (!summary) {
|
|
3442
|
+
console.error(chalk.red('Error: "summary" field is required.'));
|
|
3443
|
+
process.exit(1);
|
|
3444
|
+
}
|
|
3445
|
+
const root = getProjectRoot();
|
|
3446
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3447
|
+
try {
|
|
3448
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3449
|
+
const state = readState(root);
|
|
3450
|
+
const provider = config.provider || 'claude';
|
|
3451
|
+
config.handoff = {
|
|
3452
|
+
summary,
|
|
3453
|
+
lastProvider: provider,
|
|
3454
|
+
lastStep: state?.step,
|
|
3455
|
+
lastUpdated: new Date().toISOString(),
|
|
3456
|
+
};
|
|
3457
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
3458
|
+
console.log(chalk.green('✓ Handoff summary updated in .codeyam/config.json'));
|
|
3459
|
+
}
|
|
3460
|
+
catch (err) {
|
|
3461
|
+
console.error(chalk.red(`Error: Could not update config.json: ${err.message}`));
|
|
3462
|
+
process.exit(1);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
2709
3465
|
/**
|
|
2710
3466
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
2711
3467
|
*
|
|
@@ -2736,6 +3492,8 @@ async function handleRegister(jsonArg) {
|
|
|
2736
3492
|
const isBatch = items.length > 1;
|
|
2737
3493
|
let succeeded = 0;
|
|
2738
3494
|
let failed = 0;
|
|
3495
|
+
let warnings = 0;
|
|
3496
|
+
const issues = [];
|
|
2739
3497
|
const screenshotEntries = [];
|
|
2740
3498
|
for (let i = 0; i < items.length; i++) {
|
|
2741
3499
|
const body = items[i];
|
|
@@ -2805,11 +3563,14 @@ async function handleRegister(jsonArg) {
|
|
|
2805
3563
|
}
|
|
2806
3564
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2807
3565
|
}
|
|
3566
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2808
3567
|
if (!res.ok) {
|
|
2809
3568
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2810
3569
|
failed++;
|
|
2811
3570
|
}
|
|
2812
3571
|
else {
|
|
3572
|
+
if (resultIssues.length > 0)
|
|
3573
|
+
warnings++;
|
|
2813
3574
|
succeeded++;
|
|
2814
3575
|
// Collect screenshot paths for duplicate detection
|
|
2815
3576
|
if (data.screenshotCaptured &&
|
|
@@ -2822,6 +3583,10 @@ async function handleRegister(jsonArg) {
|
|
|
2822
3583
|
});
|
|
2823
3584
|
}
|
|
2824
3585
|
}
|
|
3586
|
+
// Collect issues from this registration
|
|
3587
|
+
for (const issue of resultIssues) {
|
|
3588
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
3589
|
+
}
|
|
2825
3590
|
}
|
|
2826
3591
|
catch (error) {
|
|
2827
3592
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -2833,7 +3598,7 @@ async function handleRegister(jsonArg) {
|
|
|
2833
3598
|
}
|
|
2834
3599
|
// Detect duplicate screenshots in the batch
|
|
2835
3600
|
if (isBatch && screenshotEntries.length > 1) {
|
|
2836
|
-
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
3601
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2837
3602
|
const hashEntries = screenshotEntries
|
|
2838
3603
|
.map((e) => {
|
|
2839
3604
|
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
@@ -2859,9 +3624,22 @@ async function handleRegister(jsonArg) {
|
|
|
2859
3624
|
}
|
|
2860
3625
|
}
|
|
2861
3626
|
if (isBatch) {
|
|
2862
|
-
console.log(
|
|
3627
|
+
console.log('');
|
|
3628
|
+
if (failed > 0 || warnings > 0) {
|
|
3629
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
3630
|
+
console.log('');
|
|
3631
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
3632
|
+
for (const issue of issues) {
|
|
3633
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
3634
|
+
}
|
|
3635
|
+
console.log('');
|
|
3636
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
3637
|
+
}
|
|
3638
|
+
else {
|
|
3639
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
3640
|
+
}
|
|
2863
3641
|
}
|
|
2864
|
-
if (failed > 0) {
|
|
3642
|
+
if (failed > 0 || warnings > 0) {
|
|
2865
3643
|
process.exit(1);
|
|
2866
3644
|
}
|
|
2867
3645
|
}
|
|
@@ -3150,10 +3928,11 @@ function handleChange(feature) {
|
|
|
3150
3928
|
* Fetch the audit result from the server.
|
|
3151
3929
|
* Returns null if the server is unreachable.
|
|
3152
3930
|
*/
|
|
3153
|
-
async function fetchAuditResult() {
|
|
3931
|
+
async function fetchAuditResult(options) {
|
|
3154
3932
|
const port = getServerPort();
|
|
3933
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3155
3934
|
try {
|
|
3156
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3935
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3157
3936
|
if (!res.ok)
|
|
3158
3937
|
return null;
|
|
3159
3938
|
return await res.json();
|
|
@@ -3176,17 +3955,15 @@ async function checkAuditGate() {
|
|
|
3176
3955
|
return true; // Server unreachable — don't block
|
|
3177
3956
|
if (data.summary?.allPassing === true)
|
|
3178
3957
|
return true;
|
|
3179
|
-
//
|
|
3180
|
-
//
|
|
3181
|
-
const {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
}
|
|
3189
|
-
// Backfill entity_sha on scenarios registered before entities existed
|
|
3958
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3959
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3960
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3961
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3962
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3963
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3964
|
+
return true;
|
|
3965
|
+
}
|
|
3966
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
3190
3967
|
try {
|
|
3191
3968
|
const entities = await loadEntities({});
|
|
3192
3969
|
if (entities && entities.length > 0) {
|
|
@@ -3204,8 +3981,8 @@ async function checkAuditGate() {
|
|
|
3204
3981
|
catch {
|
|
3205
3982
|
// Fall through
|
|
3206
3983
|
}
|
|
3207
|
-
// Re-check after
|
|
3208
|
-
const retry = await fetchAuditResult();
|
|
3984
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3985
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3209
3986
|
if (retry?.summary?.allPassing === true)
|
|
3210
3987
|
return true;
|
|
3211
3988
|
}
|
|
@@ -3228,6 +4005,8 @@ function printAuditGateFailures(data) {
|
|
|
3228
4005
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3229
4006
|
for (const c of missing) {
|
|
3230
4007
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
4008
|
+
if (c.hint)
|
|
4009
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3231
4010
|
}
|
|
3232
4011
|
}
|
|
3233
4012
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3241,7 +4020,15 @@ function printAuditGateFailures(data) {
|
|
|
3241
4020
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3242
4021
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3243
4022
|
for (const f of missing) {
|
|
3244
|
-
|
|
4023
|
+
if (f.testFile) {
|
|
4024
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
4025
|
+
}
|
|
4026
|
+
else {
|
|
4027
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
4028
|
+
if (f.suggestedTestFile) {
|
|
4029
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
3245
4032
|
}
|
|
3246
4033
|
}
|
|
3247
4034
|
if (s.functionsFailing > 0) {
|
|
@@ -3256,6 +4043,10 @@ function printAuditGateFailures(data) {
|
|
|
3256
4043
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3257
4044
|
for (const f of runnerErrors) {
|
|
3258
4045
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4046
|
+
if (f.hint)
|
|
4047
|
+
issues.push(` ${f.hint}`);
|
|
4048
|
+
else if (f.errorMessage)
|
|
4049
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
3259
4050
|
}
|
|
3260
4051
|
}
|
|
3261
4052
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3263,13 +4054,44 @@ function printAuditGateFailures(data) {
|
|
|
3263
4054
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3264
4055
|
for (const f of mismatch) {
|
|
3265
4056
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4057
|
+
if (f.hint)
|
|
4058
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3266
4059
|
}
|
|
3267
4060
|
}
|
|
3268
|
-
if (s.missingFromGlossary > 0)
|
|
4061
|
+
if (s.missingFromGlossary > 0) {
|
|
3269
4062
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
4063
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
4064
|
+
for (const m of missingGlossary) {
|
|
4065
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
4066
|
+
}
|
|
4067
|
+
const missingPaths = missingGlossary
|
|
4068
|
+
.map((m) => m.filePath)
|
|
4069
|
+
.filter(Boolean);
|
|
4070
|
+
if (missingPaths.length > 0) {
|
|
4071
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
|
|
4072
|
+
}
|
|
4073
|
+
else {
|
|
4074
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
3270
4077
|
if (s.incompleteEntities > 0) {
|
|
4078
|
+
// Check for persistent analysis failures
|
|
4079
|
+
let analysisFailures = {};
|
|
4080
|
+
try {
|
|
4081
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
4082
|
+
if (fs.existsSync(failuresPath)) {
|
|
4083
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
catch {
|
|
4087
|
+
// Non-fatal
|
|
4088
|
+
}
|
|
3271
4089
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3272
|
-
|
|
4090
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
4091
|
+
if (hasFailures) {
|
|
4092
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
4093
|
+
}
|
|
4094
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3273
4095
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3274
4096
|
}
|
|
3275
4097
|
else if (preCount > 0) {
|
|
@@ -3278,18 +4100,52 @@ function printAuditGateFailures(data) {
|
|
|
3278
4100
|
else {
|
|
3279
4101
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3280
4102
|
}
|
|
4103
|
+
const incomplete = data.incompleteEntities || [];
|
|
4104
|
+
for (const e of incomplete) {
|
|
4105
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4106
|
+
if (failureEntry) {
|
|
4107
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
4108
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
4109
|
+
}
|
|
4110
|
+
else {
|
|
4111
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
if (!hasFailures) {
|
|
4115
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
4116
|
+
}
|
|
3281
4117
|
}
|
|
3282
4118
|
if (s.unassociatedScenarios > 0) {
|
|
3283
|
-
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3284
4119
|
const unassociated = data.unassociatedScenarios || [];
|
|
4120
|
+
const unassocPaths = unassociated
|
|
4121
|
+
.map((u) => u.filePath)
|
|
4122
|
+
.filter(Boolean);
|
|
4123
|
+
if (unassocPaths.length > 0) {
|
|
4124
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
|
|
4125
|
+
}
|
|
4126
|
+
else {
|
|
4127
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
4128
|
+
}
|
|
3285
4129
|
for (const u of unassociated) {
|
|
3286
4130
|
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3287
4131
|
}
|
|
3288
4132
|
}
|
|
3289
|
-
if (s.miscategorizedScenarios > 0)
|
|
4133
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3290
4134
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3291
|
-
|
|
4135
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
4136
|
+
for (const m of miscategorized) {
|
|
4137
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
4138
|
+
}
|
|
4139
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
4140
|
+
}
|
|
4141
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
3292
4142
|
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
4143
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
4144
|
+
for (const r of recapture) {
|
|
4145
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
4146
|
+
}
|
|
4147
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
4148
|
+
}
|
|
3293
4149
|
if (issues.length > 0) {
|
|
3294
4150
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3295
4151
|
for (const issue of issues) {
|
|
@@ -3324,28 +4180,22 @@ function printAuditGateFailures(data) {
|
|
|
3324
4180
|
* which glossary components have registered scenarios and which functions
|
|
3325
4181
|
* have test files. Exits with code 1 if anything is missing.
|
|
3326
4182
|
*/
|
|
3327
|
-
async function handleAudit() {
|
|
4183
|
+
async function handleAudit(options) {
|
|
3328
4184
|
let data = await fetchAuditResult();
|
|
3329
4185
|
if (!data) {
|
|
3330
4186
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3331
4187
|
process.exit(1);
|
|
3332
4188
|
}
|
|
3333
|
-
//
|
|
3334
|
-
//
|
|
3335
|
-
//
|
|
3336
|
-
//
|
|
4189
|
+
// Two-phase auto-fix for entity associations:
|
|
4190
|
+
// Phase 1: DB-only backfill — fast, matches existing entities to scenarios.
|
|
4191
|
+
// Phase 2: If backfill fails, targeted analyze-imports for only the
|
|
4192
|
+
// specific unresolved files (1-3 files, not the full glossary scan).
|
|
3337
4193
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3338
4194
|
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3339
4195
|
let autoRemediationFailed = false;
|
|
3340
4196
|
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
3341
4197
|
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
3342
|
-
console.log(chalk.dim(`
|
|
3343
|
-
try {
|
|
3344
|
-
await handleAnalyzeImports({ silent: true });
|
|
3345
|
-
}
|
|
3346
|
-
catch {
|
|
3347
|
-
// Fall through — the audit will still report them
|
|
3348
|
-
}
|
|
4198
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
3349
4199
|
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3350
4200
|
try {
|
|
3351
4201
|
const entities = await loadEntities({});
|
|
@@ -3364,20 +4214,65 @@ async function handleAudit() {
|
|
|
3364
4214
|
catch {
|
|
3365
4215
|
// Fall through — re-fetch will show remaining issues
|
|
3366
4216
|
}
|
|
3367
|
-
// Re-fetch audit results after the
|
|
3368
|
-
|
|
4217
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
4218
|
+
// since they haven't changed (backfill is DB-only).
|
|
4219
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3369
4220
|
if (!data) {
|
|
3370
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
4221
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3371
4222
|
process.exit(1);
|
|
3372
4223
|
}
|
|
3373
|
-
// If issues persist after
|
|
4224
|
+
// If issues persist after DB-only backfill, run targeted analyze-imports
|
|
4225
|
+
// for ONLY the specific files that need entities. This is fast (1-3 files)
|
|
4226
|
+
// unlike full analyze-imports which scans all glossary entries.
|
|
4227
|
+
const incompleteAfterBackfill = data.incompleteEntities || [];
|
|
4228
|
+
const unassociatedAfterBackfill = data.unassociatedScenarios || [];
|
|
4229
|
+
if (incompleteAfterBackfill.length > 0 ||
|
|
4230
|
+
unassociatedAfterBackfill.length > 0) {
|
|
4231
|
+
const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
|
|
4232
|
+
const targetPaths = determineTargetedAnalysisPaths({
|
|
4233
|
+
unassociatedScenarios: unassociatedAfterBackfill,
|
|
4234
|
+
incompleteEntities: incompleteAfterBackfill,
|
|
4235
|
+
});
|
|
4236
|
+
if (targetPaths.length > 0) {
|
|
4237
|
+
console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
|
|
4238
|
+
try {
|
|
4239
|
+
await handleAnalyzeImports({
|
|
4240
|
+
silent: true,
|
|
4241
|
+
filePaths: targetPaths,
|
|
4242
|
+
});
|
|
4243
|
+
// Retry backfill after analysis created entities
|
|
4244
|
+
const entities = await loadEntities({});
|
|
4245
|
+
if (entities && entities.length > 0) {
|
|
4246
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4247
|
+
const freshDb = getDb();
|
|
4248
|
+
await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
|
|
4249
|
+
sha: e.sha,
|
|
4250
|
+
name: e.name,
|
|
4251
|
+
filePath: e.filePath || '',
|
|
4252
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4253
|
+
e.metadata?.namedExport === false,
|
|
4254
|
+
})));
|
|
4255
|
+
}
|
|
4256
|
+
// Re-fetch audit results after targeted analysis
|
|
4257
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
4258
|
+
if (!data) {
|
|
4259
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
|
|
4260
|
+
process.exit(1);
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
4263
|
+
catch {
|
|
4264
|
+
// Targeted analysis failed — fall through to show remaining issues
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
// Check if issues persist after all remediation attempts
|
|
3374
4269
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3375
4270
|
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3376
4271
|
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3377
4272
|
autoRemediationFailed = true;
|
|
3378
4273
|
}
|
|
3379
4274
|
}
|
|
3380
|
-
|
|
4275
|
+
let { components, functions, summary } = data;
|
|
3381
4276
|
console.log();
|
|
3382
4277
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
3383
4278
|
console.log();
|
|
@@ -3407,6 +4302,9 @@ async function handleAudit() {
|
|
|
3407
4302
|
}
|
|
3408
4303
|
else {
|
|
3409
4304
|
detail = chalk.red(' — no scenarios registered');
|
|
4305
|
+
if (c.hint) {
|
|
4306
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
4307
|
+
}
|
|
3410
4308
|
}
|
|
3411
4309
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3412
4310
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3432,14 +4330,20 @@ async function handleAudit() {
|
|
|
3432
4330
|
let detail;
|
|
3433
4331
|
switch (f.status) {
|
|
3434
4332
|
case 'ok':
|
|
3435
|
-
|
|
4333
|
+
if (f.testCaseCount !== undefined && f.testCaseCount < 3) {
|
|
4334
|
+
detail = chalk.yellow(` (${f.testFile}) — ⚠ only ${f.testCaseCount} test case${f.testCaseCount !== 1 ? 's' : ''}, consider adding more`);
|
|
4335
|
+
}
|
|
4336
|
+
else {
|
|
4337
|
+
detail = chalk.dim(` (${f.testFile}${f.testCaseCount !== undefined ? `, ${f.testCaseCount} tests` : ''})`);
|
|
4338
|
+
}
|
|
3436
4339
|
break;
|
|
3437
4340
|
case 'runner_error':
|
|
3438
4341
|
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3439
4342
|
if (f.errorMessage) {
|
|
3440
4343
|
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3441
4344
|
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3442
|
-
|
|
4345
|
+
const ctx = getTechStackContext(process.cwd());
|
|
4346
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
3443
4347
|
}
|
|
3444
4348
|
break;
|
|
3445
4349
|
case 'failing':
|
|
@@ -3450,13 +4354,23 @@ async function handleAudit() {
|
|
|
3450
4354
|
break;
|
|
3451
4355
|
case 'missing':
|
|
3452
4356
|
default:
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
4357
|
+
if (f.testFile) {
|
|
4358
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
4359
|
+
}
|
|
4360
|
+
else {
|
|
4361
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
4362
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
4363
|
+
if (f.suggestedTestFile) {
|
|
4364
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
3456
4367
|
break;
|
|
3457
4368
|
}
|
|
3458
4369
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
3459
4370
|
}
|
|
4371
|
+
if (summary.functionsThinCoverage > 0) {
|
|
4372
|
+
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.`));
|
|
4373
|
+
}
|
|
3460
4374
|
console.log();
|
|
3461
4375
|
}
|
|
3462
4376
|
// Missing from glossary
|
|
@@ -3466,22 +4380,53 @@ async function handleAudit() {
|
|
|
3466
4380
|
for (const m of missingFromGlossary) {
|
|
3467
4381
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3468
4382
|
}
|
|
3469
|
-
|
|
4383
|
+
const mgPaths = missingFromGlossary
|
|
4384
|
+
.map((m) => m.filePath)
|
|
4385
|
+
.filter(Boolean);
|
|
4386
|
+
if (mgPaths.length > 0) {
|
|
4387
|
+
console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
|
|
4388
|
+
}
|
|
4389
|
+
else {
|
|
4390
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
4391
|
+
}
|
|
3470
4392
|
console.log();
|
|
3471
4393
|
}
|
|
3472
|
-
// Incomplete entities (scenarios without analyses)
|
|
4394
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
4395
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
4396
|
+
// template which is slow even for a few files. Users should run
|
|
4397
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3473
4398
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3474
4399
|
if (incompleteEntities.length > 0) {
|
|
4400
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
4401
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
4402
|
+
// Check for persistent analysis failures
|
|
4403
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3475
4404
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3476
4405
|
for (const e of incompleteEntities) {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
4406
|
+
// Check if this entity has a persistent failure
|
|
4407
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4408
|
+
if (failureEntry) {
|
|
4409
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
4410
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
4411
|
+
const guidance = formatManualAnalysisGuidance({
|
|
4412
|
+
name: e.name,
|
|
4413
|
+
filePath: filePath || '',
|
|
4414
|
+
scenarioCount: e.scenarioCount,
|
|
4415
|
+
error: failureEntry.error,
|
|
4416
|
+
});
|
|
4417
|
+
// Print each line with proper indentation
|
|
4418
|
+
for (const line of guidance.split('\n')) {
|
|
4419
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
else {
|
|
4423
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
4424
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
4425
|
+
}
|
|
3482
4426
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
4427
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4428
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
4429
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3485
4430
|
}
|
|
3486
4431
|
console.log();
|
|
3487
4432
|
}
|
|
@@ -3498,12 +4443,22 @@ async function handleAudit() {
|
|
|
3498
4443
|
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
3499
4444
|
}
|
|
3500
4445
|
}
|
|
4446
|
+
const uaPaths = unassociatedScenarios
|
|
4447
|
+
.map((u) => u.filePath)
|
|
4448
|
+
.filter(Boolean);
|
|
4449
|
+
const uaCmd = uaPaths.length > 0
|
|
4450
|
+
? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
|
|
4451
|
+
: 'codeyam editor analyze-imports';
|
|
3501
4452
|
if (autoRemediationFailed) {
|
|
3502
4453
|
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
3503
|
-
console.log(chalk.yellow(
|
|
4454
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
|
|
3504
4455
|
}
|
|
3505
4456
|
else {
|
|
3506
|
-
console.log(chalk.yellow(
|
|
4457
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
|
|
4458
|
+
}
|
|
4459
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4460
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
4461
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3507
4462
|
}
|
|
3508
4463
|
console.log();
|
|
3509
4464
|
}
|
|
@@ -3532,7 +4487,26 @@ async function handleAudit() {
|
|
|
3532
4487
|
: `${s.status.status}`;
|
|
3533
4488
|
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3534
4489
|
}
|
|
3535
|
-
|
|
4490
|
+
if (options?.fix) {
|
|
4491
|
+
// --fix: auto-recapture stale scenarios instead of just reporting them
|
|
4492
|
+
const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
|
|
4493
|
+
if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
|
|
4494
|
+
console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
|
|
4495
|
+
console.log();
|
|
4496
|
+
await handleRecaptureStale();
|
|
4497
|
+
// Re-fetch audit results so the summary and exit code reflect
|
|
4498
|
+
// the post-fix state, not the pre-fix state.
|
|
4499
|
+
const refreshed = await fetchAuditResult({ skipTests: true });
|
|
4500
|
+
if (refreshed) {
|
|
4501
|
+
data = refreshed;
|
|
4502
|
+
({ components, functions, summary } = data);
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
else {
|
|
4507
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
4508
|
+
console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
|
|
4509
|
+
}
|
|
3536
4510
|
console.log();
|
|
3537
4511
|
}
|
|
3538
4512
|
// Duplicate glossary names (warning, not a failure)
|
|
@@ -3595,17 +4569,6 @@ async function handleAudit() {
|
|
|
3595
4569
|
if (!allOk) {
|
|
3596
4570
|
process.exit(1);
|
|
3597
4571
|
}
|
|
3598
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3599
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3600
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3601
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3602
|
-
try {
|
|
3603
|
-
await handleAnalyzeImports({ silent: true });
|
|
3604
|
-
}
|
|
3605
|
-
catch {
|
|
3606
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3607
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3608
|
-
}
|
|
3609
4572
|
}
|
|
3610
4573
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3611
4574
|
/**
|
|
@@ -3812,14 +4775,14 @@ async function handleScenarioCoverage() {
|
|
|
3812
4775
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3813
4776
|
try {
|
|
3814
4777
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3815
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4778
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3816
4779
|
const db = getDatabase();
|
|
3817
4780
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3818
4781
|
if (backfillCount > 0) {
|
|
3819
4782
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3820
4783
|
await handleAnalyzeImports({ silent: true });
|
|
3821
4784
|
// Run backfill after analysis
|
|
3822
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4785
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3823
4786
|
const entities = await loadEntities({});
|
|
3824
4787
|
if (entities && entities.length > 0) {
|
|
3825
4788
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -3966,7 +4929,7 @@ async function handleTemplate() {
|
|
|
3966
4929
|
_: [],
|
|
3967
4930
|
});
|
|
3968
4931
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
3969
|
-
// 5. Verify config has startCommand
|
|
4932
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
3970
4933
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3971
4934
|
if (fs.existsSync(configPath)) {
|
|
3972
4935
|
try {
|
|
@@ -3977,6 +4940,30 @@ async function handleTemplate() {
|
|
|
3977
4940
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
3978
4941
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
3979
4942
|
}
|
|
4943
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4944
|
+
if (stack?.supportedFormats) {
|
|
4945
|
+
config.appFormats = stack.supportedFormats;
|
|
4946
|
+
}
|
|
4947
|
+
// Pre-populate tech stack from template
|
|
4948
|
+
if (stack) {
|
|
4949
|
+
config.techStack = getTechStackForTemplate(stack.id);
|
|
4950
|
+
}
|
|
4951
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4952
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4953
|
+
config.defaultScreenSize = {
|
|
4954
|
+
name: 'iPhone 16',
|
|
4955
|
+
width: 393,
|
|
4956
|
+
height: 852,
|
|
4957
|
+
};
|
|
4958
|
+
config.screenSizes = {
|
|
4959
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4960
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4961
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4962
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4963
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4964
|
+
};
|
|
4965
|
+
}
|
|
4966
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3980
4967
|
}
|
|
3981
4968
|
catch {
|
|
3982
4969
|
// Config parse error is non-fatal
|
|
@@ -4011,7 +4998,15 @@ async function handleTemplate() {
|
|
|
4011
4998
|
}
|
|
4012
4999
|
console.log();
|
|
4013
5000
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
4014
|
-
|
|
5001
|
+
if (stack?.id === 'expo-react-native') {
|
|
5002
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
5003
|
+
}
|
|
5004
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
5005
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
5006
|
+
}
|
|
5007
|
+
else {
|
|
5008
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
5009
|
+
}
|
|
4015
5010
|
}
|
|
4016
5011
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
4017
5012
|
/**
|
|
@@ -4316,14 +5311,205 @@ function handleEditorDebug(args) {
|
|
|
4316
5311
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4317
5312
|
console.log();
|
|
4318
5313
|
}
|
|
5314
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
5315
|
+
/**
|
|
5316
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
5317
|
+
*
|
|
5318
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
5319
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
5320
|
+
* needing the analyzer template to parse the source file.
|
|
5321
|
+
*/
|
|
5322
|
+
async function handleManualEntity(jsonArg) {
|
|
5323
|
+
const root = getProjectRoot();
|
|
5324
|
+
// Parse JSON input (supports @file.json convention)
|
|
5325
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
5326
|
+
if (parsed.error || !parsed.body) {
|
|
5327
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
5328
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
5329
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
5330
|
+
process.exit(1);
|
|
5331
|
+
}
|
|
5332
|
+
const input = parsed.body;
|
|
5333
|
+
// Validate input
|
|
5334
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
5335
|
+
// Read glossary for validation
|
|
5336
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
5337
|
+
let glossaryEntries = [];
|
|
5338
|
+
try {
|
|
5339
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
5340
|
+
}
|
|
5341
|
+
catch {
|
|
5342
|
+
// Empty glossary — validation will still check file existence
|
|
5343
|
+
}
|
|
5344
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
5345
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
5346
|
+
});
|
|
5347
|
+
if (errors.length > 0) {
|
|
5348
|
+
console.error(chalk.red('Validation errors:'));
|
|
5349
|
+
for (const err of errors) {
|
|
5350
|
+
console.error(chalk.red(` • ${err}`));
|
|
5351
|
+
}
|
|
5352
|
+
process.exit(1);
|
|
5353
|
+
}
|
|
5354
|
+
// Read source file and compute SHA
|
|
5355
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
5356
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
5357
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
5358
|
+
// Get project and branch
|
|
5359
|
+
await initializeEnvironment();
|
|
5360
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
5361
|
+
let projectSlug;
|
|
5362
|
+
try {
|
|
5363
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
5364
|
+
projectSlug = config.projectSlug;
|
|
5365
|
+
}
|
|
5366
|
+
catch {
|
|
5367
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
5368
|
+
process.exit(1);
|
|
5369
|
+
}
|
|
5370
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
5371
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
5372
|
+
const db = getDatabase();
|
|
5373
|
+
// Convert type info to dataForMocks format
|
|
5374
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
5375
|
+
// Create entity record
|
|
5376
|
+
const entityMetadata = {
|
|
5377
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
5378
|
+
name: ie.name,
|
|
5379
|
+
filePath: ie.filePath,
|
|
5380
|
+
})),
|
|
5381
|
+
manuallyAnalyzed: true,
|
|
5382
|
+
};
|
|
5383
|
+
// Check if entity already exists
|
|
5384
|
+
const existingEntity = await db
|
|
5385
|
+
.selectFrom('entities')
|
|
5386
|
+
.select('sha')
|
|
5387
|
+
.where('sha', '=', entitySha)
|
|
5388
|
+
.executeTakeFirst();
|
|
5389
|
+
if (!existingEntity) {
|
|
5390
|
+
await db
|
|
5391
|
+
.insertInto('entities')
|
|
5392
|
+
.values({
|
|
5393
|
+
sha: entitySha,
|
|
5394
|
+
project_id: project.id,
|
|
5395
|
+
name: input.name,
|
|
5396
|
+
entity_type: input.entityType,
|
|
5397
|
+
file_path: input.filePath,
|
|
5398
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5399
|
+
})
|
|
5400
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
5401
|
+
.execute();
|
|
5402
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5403
|
+
}
|
|
5404
|
+
else {
|
|
5405
|
+
// Update metadata on existing entity
|
|
5406
|
+
await db
|
|
5407
|
+
.updateTable('entities')
|
|
5408
|
+
.set({
|
|
5409
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5410
|
+
})
|
|
5411
|
+
.where('sha', '=', entitySha)
|
|
5412
|
+
.execute();
|
|
5413
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5414
|
+
}
|
|
5415
|
+
// Create entity_branch record
|
|
5416
|
+
await db
|
|
5417
|
+
.insertInto('entity_branches')
|
|
5418
|
+
.values({
|
|
5419
|
+
entity_sha: entitySha,
|
|
5420
|
+
branch_id: branch.id,
|
|
5421
|
+
active: true,
|
|
5422
|
+
})
|
|
5423
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
5424
|
+
.execute();
|
|
5425
|
+
// Create analysis record
|
|
5426
|
+
const { randomUUID } = await import('crypto');
|
|
5427
|
+
const analysisId = randomUUID();
|
|
5428
|
+
const now = new Date().toISOString();
|
|
5429
|
+
const analysisMetadata = {
|
|
5430
|
+
scenariosDataStructure: {
|
|
5431
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
5432
|
+
},
|
|
5433
|
+
mergedDataStructure: {
|
|
5434
|
+
dependencySchemas: null,
|
|
5435
|
+
},
|
|
5436
|
+
manuallyAnalyzed: true,
|
|
5437
|
+
};
|
|
5438
|
+
const analysisStatus = {
|
|
5439
|
+
startedAt: now,
|
|
5440
|
+
finishedAt: now,
|
|
5441
|
+
steps: [],
|
|
5442
|
+
errors: [],
|
|
5443
|
+
};
|
|
5444
|
+
// Check if analysis already exists for this entity
|
|
5445
|
+
const existingAnalysis = await db
|
|
5446
|
+
.selectFrom('analyses')
|
|
5447
|
+
.select('id')
|
|
5448
|
+
.where('entity_sha', '=', entitySha)
|
|
5449
|
+
.executeTakeFirst();
|
|
5450
|
+
if (!existingAnalysis) {
|
|
5451
|
+
await db
|
|
5452
|
+
.insertInto('analyses')
|
|
5453
|
+
.values({
|
|
5454
|
+
id: analysisId,
|
|
5455
|
+
project_id: project.id,
|
|
5456
|
+
entity_sha: entitySha,
|
|
5457
|
+
entity_name: input.name,
|
|
5458
|
+
entity_type: input.entityType,
|
|
5459
|
+
file_path: input.filePath,
|
|
5460
|
+
status: JSON.stringify(analysisStatus),
|
|
5461
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5462
|
+
})
|
|
5463
|
+
.execute();
|
|
5464
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
5465
|
+
}
|
|
5466
|
+
else {
|
|
5467
|
+
// Update existing analysis with manual metadata
|
|
5468
|
+
await db
|
|
5469
|
+
.updateTable('analyses')
|
|
5470
|
+
.set({
|
|
5471
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5472
|
+
status: JSON.stringify(analysisStatus),
|
|
5473
|
+
})
|
|
5474
|
+
.where('entity_sha', '=', entitySha)
|
|
5475
|
+
.execute();
|
|
5476
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
5477
|
+
}
|
|
5478
|
+
// Backfill entity_sha on scenarios
|
|
5479
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
5480
|
+
{
|
|
5481
|
+
sha: entitySha,
|
|
5482
|
+
name: input.name,
|
|
5483
|
+
filePath: input.filePath,
|
|
5484
|
+
isDefaultExport: false,
|
|
5485
|
+
},
|
|
5486
|
+
]);
|
|
5487
|
+
if (backfillResult.updated > 0) {
|
|
5488
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
5489
|
+
}
|
|
5490
|
+
// Clear from analysis failures tracker
|
|
5491
|
+
const failures = readAnalysisFailures(root);
|
|
5492
|
+
if (failures[input.filePath]) {
|
|
5493
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
5494
|
+
writeAnalysisFailures(root, updated);
|
|
5495
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
5496
|
+
}
|
|
5497
|
+
console.log();
|
|
5498
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
5499
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
5500
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
5501
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
5502
|
+
console.log();
|
|
5503
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
5504
|
+
}
|
|
4319
5505
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4320
5506
|
const editorCommand = {
|
|
4321
5507
|
command: 'editor [step] [json]',
|
|
4322
5508
|
describe: 'Editor mode guided workflow',
|
|
4323
5509
|
builder: (yargs) => {
|
|
4324
5510
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4325
|
-
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
|
|
4326
|
-
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
5511
|
+
? '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)'
|
|
5512
|
+
: '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)';
|
|
4327
5513
|
let builder = yargs
|
|
4328
5514
|
.positional('step', {
|
|
4329
5515
|
type: 'string',
|
|
@@ -4354,6 +5540,11 @@ const editorCommand = {
|
|
|
4354
5540
|
alias: 'p',
|
|
4355
5541
|
describe: 'Port to run the web server on',
|
|
4356
5542
|
default: 3111,
|
|
5543
|
+
})
|
|
5544
|
+
.option('fix', {
|
|
5545
|
+
type: 'boolean',
|
|
5546
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
5547
|
+
default: false,
|
|
4357
5548
|
});
|
|
4358
5549
|
if (IS_INTERNAL_BUILD) {
|
|
4359
5550
|
builder = builder
|
|
@@ -4429,6 +5620,11 @@ const editorCommand = {
|
|
|
4429
5620
|
}
|
|
4430
5621
|
return;
|
|
4431
5622
|
}
|
|
5623
|
+
// Subcommand: codeyam editor handoff '{"summary":"..."}'
|
|
5624
|
+
if (argv.step === 'handoff') {
|
|
5625
|
+
await handleHandoff(argv.json || '');
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
4432
5628
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
4433
5629
|
if (argv.step === 'register') {
|
|
4434
5630
|
await handleRegister(argv.json || '');
|
|
@@ -4465,7 +5661,9 @@ const editorCommand = {
|
|
|
4465
5661
|
'Ask user what to build next and restart codeyam editor workflow';
|
|
4466
5662
|
}
|
|
4467
5663
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4468
|
-
console.log(chalk.cyan('
|
|
5664
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
5665
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
5666
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
4469
5667
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4470
5668
|
console.log();
|
|
4471
5669
|
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
@@ -4479,9 +5677,16 @@ const editorCommand = {
|
|
|
4479
5677
|
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
4480
5678
|
return;
|
|
4481
5679
|
}
|
|
4482
|
-
// Subcommand: codeyam editor analyze-imports
|
|
5680
|
+
// Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
|
|
4483
5681
|
if (argv.step === 'analyze-imports') {
|
|
4484
|
-
await
|
|
5682
|
+
const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
|
|
5683
|
+
const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
|
|
5684
|
+
await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
|
|
5685
|
+
return;
|
|
5686
|
+
}
|
|
5687
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
5688
|
+
if (argv.step === 'manual-entity') {
|
|
5689
|
+
await handleManualEntity(argv.json || '');
|
|
4485
5690
|
return;
|
|
4486
5691
|
}
|
|
4487
5692
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -4489,9 +5694,9 @@ const editorCommand = {
|
|
|
4489
5694
|
await handleDependents(argv.json || '');
|
|
4490
5695
|
return;
|
|
4491
5696
|
}
|
|
4492
|
-
// Subcommand: codeyam editor audit
|
|
5697
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
4493
5698
|
if (argv.step === 'audit') {
|
|
4494
|
-
await handleAudit();
|
|
5699
|
+
await handleAudit({ fix: argv.fix || false });
|
|
4495
5700
|
return;
|
|
4496
5701
|
}
|
|
4497
5702
|
// Subcommand: codeyam editor scenarios
|
|
@@ -4524,6 +5729,11 @@ const editorCommand = {
|
|
|
4524
5729
|
await handleValidateSeed(argv.json || '');
|
|
4525
5730
|
return;
|
|
4526
5731
|
}
|
|
5732
|
+
// Subcommand: codeyam editor design-system <id>
|
|
5733
|
+
if (argv.step === 'design-system') {
|
|
5734
|
+
handleDesignSystem(argv.json || '');
|
|
5735
|
+
return;
|
|
5736
|
+
}
|
|
4527
5737
|
// Subcommand: codeyam editor delete <scenarioId>
|
|
4528
5738
|
if (argv.step === 'delete') {
|
|
4529
5739
|
await handleDelete(argv.json || '');
|
|
@@ -4597,6 +5807,13 @@ const editorCommand = {
|
|
|
4597
5807
|
try {
|
|
4598
5808
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4599
5809
|
const { projectSlug } = config;
|
|
5810
|
+
// Detect provider switch and inform the user
|
|
5811
|
+
const handoff = config.handoff;
|
|
5812
|
+
const currentProvider = config.provider || 'claude';
|
|
5813
|
+
if (handoff?.lastProvider && handoff.lastProvider !== currentProvider) {
|
|
5814
|
+
console.log(chalk.yellow(` Provider changed: ${handoff.lastProvider} → ${currentProvider}`));
|
|
5815
|
+
console.log(chalk.dim(' The editor will offer to continue with handoff context.'));
|
|
5816
|
+
}
|
|
4600
5817
|
if (!projectSlug) {
|
|
4601
5818
|
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
4602
5819
|
return;
|
|
@@ -4724,13 +5941,24 @@ const editorCommand = {
|
|
|
4724
5941
|
try {
|
|
4725
5942
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4726
5943
|
const seedDb = getDb();
|
|
4727
|
-
|
|
5944
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5945
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5946
|
+
const homeScenario = await seedDb
|
|
4728
5947
|
.selectFrom('editor_scenarios')
|
|
4729
5948
|
.select(['id', 'name', 'type'])
|
|
4730
5949
|
.where('project_id', '=', project.id)
|
|
4731
|
-
.where('
|
|
5950
|
+
.where('url', '=', '/')
|
|
5951
|
+
.where('component_name', 'is', null)
|
|
4732
5952
|
.orderBy('created_at', 'asc')
|
|
4733
5953
|
.executeTakeFirst();
|
|
5954
|
+
const appScenario = homeScenario ||
|
|
5955
|
+
(await seedDb
|
|
5956
|
+
.selectFrom('editor_scenarios')
|
|
5957
|
+
.select(['id', 'name', 'type'])
|
|
5958
|
+
.where('project_id', '=', project.id)
|
|
5959
|
+
.where('type', '=', 'application')
|
|
5960
|
+
.orderBy('created_at', 'asc')
|
|
5961
|
+
.executeTakeFirst());
|
|
4734
5962
|
if (appScenario) {
|
|
4735
5963
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4736
5964
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4873,7 +6101,7 @@ const editorCommand = {
|
|
|
4873
6101
|
if (!needsAnalysis) {
|
|
4874
6102
|
try {
|
|
4875
6103
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4876
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
6104
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4877
6105
|
const db = getDatabase();
|
|
4878
6106
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4879
6107
|
if (backfillCount > 0) {
|
|
@@ -4912,7 +6140,7 @@ const editorCommand = {
|
|
|
4912
6140
|
.execute();
|
|
4913
6141
|
if (unresolved.length > 0) {
|
|
4914
6142
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4915
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
6143
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4916
6144
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4917
6145
|
let pfpResolved = 0;
|
|
4918
6146
|
for (const row of unresolved) {
|
|
@@ -5001,6 +6229,9 @@ const editorCommand = {
|
|
|
5001
6229
|
process.exit(1);
|
|
5002
6230
|
}
|
|
5003
6231
|
}
|
|
6232
|
+
printHandoffContext(root);
|
|
6233
|
+
// Track step progress automatically for provider handoff
|
|
6234
|
+
updateHandoffProgress(root, step);
|
|
5004
6235
|
switch (step) {
|
|
5005
6236
|
case 1: {
|
|
5006
6237
|
const feature = argv.feature || undefined;
|