@codeyam/codeyam-cli 0.1.0-staging.30dc541 → 0.1.0-staging.346f50f
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 +4 -4
- package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
- package/analyzer-template/packages/analyze/index.ts +4 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
- package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +10 -6
- package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +9 -12
- package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
- package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
- package/analyzer-template/packages/aws/package.json +6 -6
- package/analyzer-template/packages/database/package.json +2 -2
- package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +25 -15
- package/analyzer-template/packages/database/src/lib/loadEntity.ts +19 -8
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +7 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +5 -5
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
- package/analyzer-template/project/analyzeFileEntities.ts +26 -0
- package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
- package/background/src/lib/virtualized/project/analyzeFileEntities.js +22 -0
- package/background/src/lib/virtualized/project/analyzeFileEntities.js.map +1 -1
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
- package/codeyam-cli/src/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 +1648 -225
- 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__/devServerState.test.js +93 -1
- package/codeyam-cli/src/utils/__tests__/devServerState.test.js.map +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 +2387 -907
- 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 +160 -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__/screenshotHash.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/analysisRunner.js +36 -7
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +11 -1
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
- package/codeyam-cli/src/utils/devServerState.js +32 -0
- package/codeyam-cli/src/utils/devServerState.js.map +1 -1
- 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 +345 -22
- 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 +107 -4
- 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 +35 -6
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
- package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/screenshotHash.js +26 -0
- package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
- package/codeyam-cli/src/utils/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 +119 -1
- package/codeyam-cli/src/webserver/__tests__/buildPtyEnv.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +28 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +384 -12
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +89 -2
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +57 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +15 -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)-DhtVC4aI.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-79d0d81a.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-L2V0jea7.js +80 -0
- 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-QgInFGdU.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-Cd-ufawF.js → index-zblh9auj.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/init-DaE0CBjk.js +14 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CNvgz1cC.js +853 -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 +310 -23
- 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/scripts/journalCapture.ts +17 -0
- package/codeyam-cli/src/webserver/server.js +119 -14
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +173 -27
- 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 +25 -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 +34 -1
- package/package.json +1 -1
- package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
- package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
- package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
- package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
- package/packages/ai/src/lib/astScopes/paths.js +12 -3
- package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
- package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
- package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/database/src/lib/loadAnalysis.js +7 -1
- package/packages/database/src/lib/loadAnalysis.js.map +1 -1
- package/packages/database/src/lib/loadEntity.js +5 -5
- package/packages/database/src/lib/loadEntity.js.map +1 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-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)-CGzKlIHg.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-2ef99f38.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-BxUQigda.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BNd5hYuW.js +0 -2
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-BPmOG9bE.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/init-CzeBGOto.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-Dht7CKXY.js +0 -552
- 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,8 +22,11 @@ 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";
|
|
29
|
+
import { getDisplayVersion } from "../utils/versionInfo.js";
|
|
26
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
31
|
const __dirname = path.dirname(__filename);
|
|
28
32
|
const STEP_LABELS = {
|
|
@@ -121,6 +125,13 @@ function writeState(root, state) {
|
|
|
121
125
|
const dir = path.dirname(statePath);
|
|
122
126
|
fs.mkdirSync(dir, { recursive: true });
|
|
123
127
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
128
|
+
// Write task tracking file — marks that a task is expected for this step.
|
|
129
|
+
// The step hook sets taskCreated=true when it sees a TaskCreate tool call.
|
|
130
|
+
// Steps 1 and below don't require tasks (feature not named yet).
|
|
131
|
+
if (state.step && state.step >= 2) {
|
|
132
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
133
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: false }, null, 2), 'utf8');
|
|
134
|
+
}
|
|
124
135
|
}
|
|
125
136
|
/**
|
|
126
137
|
* Clear the editor state (for starting a new feature).
|
|
@@ -180,6 +191,181 @@ function getProjectDimensions(root) {
|
|
|
180
191
|
return { defaultName: 'Desktop', names: [] };
|
|
181
192
|
}
|
|
182
193
|
}
|
|
194
|
+
function getTechStackContext(root) {
|
|
195
|
+
const state = readState(root);
|
|
196
|
+
const techStackId = state?.techStackId || '';
|
|
197
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
198
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
199
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
200
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
201
|
+
return {
|
|
202
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
203
|
+
isExpo,
|
|
204
|
+
isNextjs,
|
|
205
|
+
isChromeExt,
|
|
206
|
+
hasDatabase: isNextjs,
|
|
207
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
208
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
209
|
+
storageType: isExpo
|
|
210
|
+
? 'asyncStorage'
|
|
211
|
+
: isChromeExt
|
|
212
|
+
? 'chromeStorage'
|
|
213
|
+
: 'prisma',
|
|
214
|
+
routerImport: isExpo
|
|
215
|
+
? 'expo-router'
|
|
216
|
+
: isChromeExt
|
|
217
|
+
? 'react-router-dom'
|
|
218
|
+
: 'next/navigation',
|
|
219
|
+
componentPrimitives: isExpo
|
|
220
|
+
? '<View>, <Text>, <ScrollView>'
|
|
221
|
+
: '<div>, <span>, <h1>',
|
|
222
|
+
rawPrimitivesList: isExpo
|
|
223
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
224
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
225
|
+
patternsFile: isExpo
|
|
226
|
+
? 'MOBILE_SETUP.md'
|
|
227
|
+
: isChromeExt
|
|
228
|
+
? 'EXTENSION_SETUP.md'
|
|
229
|
+
: 'FEATURE_PATTERNS.md',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns a pre-populated tech stack for a given template ID.
|
|
234
|
+
* Written to .codeyam/config.json during `codeyam editor template`.
|
|
235
|
+
*/
|
|
236
|
+
function getTechStackForTemplate(templateId) {
|
|
237
|
+
switch (templateId) {
|
|
238
|
+
case 'nextjs-prisma-sqlite':
|
|
239
|
+
return {
|
|
240
|
+
languages: [
|
|
241
|
+
{
|
|
242
|
+
name: 'TypeScript',
|
|
243
|
+
url: 'https://typescriptlang.org',
|
|
244
|
+
description: 'Statically typed JavaScript superset',
|
|
245
|
+
version: '5',
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
frameworks: [
|
|
249
|
+
{
|
|
250
|
+
name: 'Next.js',
|
|
251
|
+
url: 'https://nextjs.org',
|
|
252
|
+
description: 'Full-stack React framework with App Router, SSR, and API routes',
|
|
253
|
+
version: '15',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'React',
|
|
257
|
+
url: 'https://react.dev',
|
|
258
|
+
description: 'Component-based UI library',
|
|
259
|
+
version: '19',
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
databases: [
|
|
263
|
+
{
|
|
264
|
+
name: 'SQLite',
|
|
265
|
+
url: 'https://sqlite.org',
|
|
266
|
+
description: 'Embedded relational database — zero config, upgradeable to hosted DB',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
libraries: [
|
|
270
|
+
{
|
|
271
|
+
name: 'Prisma',
|
|
272
|
+
url: 'https://prisma.io',
|
|
273
|
+
description: 'Type-safe database ORM and query builder',
|
|
274
|
+
version: '7',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'Tailwind CSS',
|
|
278
|
+
url: 'https://tailwindcss.com',
|
|
279
|
+
description: 'Utility-first CSS framework',
|
|
280
|
+
version: '4',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
case 'chrome-extension-react':
|
|
285
|
+
return {
|
|
286
|
+
languages: [
|
|
287
|
+
{
|
|
288
|
+
name: 'TypeScript',
|
|
289
|
+
url: 'https://typescriptlang.org',
|
|
290
|
+
description: 'Statically typed JavaScript superset',
|
|
291
|
+
version: '5',
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
frameworks: [
|
|
295
|
+
{
|
|
296
|
+
name: 'React',
|
|
297
|
+
url: 'https://react.dev',
|
|
298
|
+
description: 'Component-based UI library for the popup and options pages',
|
|
299
|
+
version: '19',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: 'Vite',
|
|
303
|
+
url: 'https://vite.dev',
|
|
304
|
+
description: 'Fast build tool and dev server',
|
|
305
|
+
version: '6',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
libraries: [
|
|
309
|
+
{
|
|
310
|
+
name: 'Tailwind CSS',
|
|
311
|
+
url: 'https://tailwindcss.com',
|
|
312
|
+
description: 'Utility-first CSS framework',
|
|
313
|
+
version: '4',
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
infrastructure: [
|
|
317
|
+
{
|
|
318
|
+
name: 'Chrome Manifest V3',
|
|
319
|
+
url: 'https://developer.chrome.com/docs/extensions/develop/migrate/what-is-mv3',
|
|
320
|
+
description: 'Extension platform with service workers, declarative APIs, and chrome.storage',
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
case 'expo-react-native':
|
|
325
|
+
return {
|
|
326
|
+
languages: [
|
|
327
|
+
{
|
|
328
|
+
name: 'TypeScript',
|
|
329
|
+
url: 'https://typescriptlang.org',
|
|
330
|
+
description: 'Statically typed JavaScript superset',
|
|
331
|
+
version: '5',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
frameworks: [
|
|
335
|
+
{
|
|
336
|
+
name: 'Expo',
|
|
337
|
+
url: 'https://expo.dev',
|
|
338
|
+
description: 'React Native development platform with managed workflow and OTA updates',
|
|
339
|
+
version: 'SDK 54',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'React Native',
|
|
343
|
+
url: 'https://reactnative.dev',
|
|
344
|
+
description: 'Cross-platform mobile UI framework',
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'Expo Router',
|
|
348
|
+
url: 'https://docs.expo.dev/router/introduction/',
|
|
349
|
+
description: 'File-based navigation for React Native apps',
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
libraries: [
|
|
353
|
+
{
|
|
354
|
+
name: 'NativeWind',
|
|
355
|
+
url: 'https://www.nativewind.dev',
|
|
356
|
+
description: 'Tailwind CSS for React Native',
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'AsyncStorage',
|
|
360
|
+
url: 'https://react-native-async-storage.github.io/async-storage/',
|
|
361
|
+
description: 'Persistent key-value storage for React Native',
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
};
|
|
365
|
+
default:
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
183
369
|
/**
|
|
184
370
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
185
371
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -203,6 +389,86 @@ function checkbox(text) {
|
|
|
203
389
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
204
390
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
205
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Print a checklist item for the handoff summary.
|
|
394
|
+
*/
|
|
395
|
+
function checkboxHandoff() {
|
|
396
|
+
checkbox('Update the handoff summary in `.codeyam/config.json` for the next session/AI: `codeyam editor handoff \'{"summary":"..."}\'`');
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Print the handoff context from a previous session or AI provider.
|
|
400
|
+
* Prefers the auto-generated handoff-context.md over the raw config summary.
|
|
401
|
+
*/
|
|
402
|
+
function printHandoffContext(root) {
|
|
403
|
+
try {
|
|
404
|
+
// Check for auto-generated handoff context (richer, includes progress timeline)
|
|
405
|
+
const handoffContextPath = path.join(root, '.codeyam', 'handoff-context.md');
|
|
406
|
+
if (fs.existsSync(handoffContextPath)) {
|
|
407
|
+
const content = fs.readFileSync(handoffContextPath, 'utf8');
|
|
408
|
+
console.log();
|
|
409
|
+
console.log(chalk.bold.magenta('━━━ PROVIDER HANDOFF CONTEXT ━━━'));
|
|
410
|
+
console.log(chalk.magenta(content));
|
|
411
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
412
|
+
console.log();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Fall back to config.json handoff summary
|
|
416
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
417
|
+
if (!fs.existsSync(configPath))
|
|
418
|
+
return;
|
|
419
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
420
|
+
const handoff = config.handoff;
|
|
421
|
+
if (!handoff || !handoff.summary)
|
|
422
|
+
return;
|
|
423
|
+
console.log();
|
|
424
|
+
console.log(chalk.bold.magenta(`━━━ HANDOFF FROM ${String(handoff.lastProvider || 'unknown').toUpperCase()} (Step ${handoff.lastStep || 'unknown'}) ━━━`));
|
|
425
|
+
console.log(chalk.magenta(handoff.summary));
|
|
426
|
+
if (handoff.lastUpdated) {
|
|
427
|
+
console.log(chalk.dim(` Updated: ${handoff.lastUpdated}`));
|
|
428
|
+
}
|
|
429
|
+
console.log(chalk.bold.magenta('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
430
|
+
console.log();
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// Non-fatal
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
438
|
+
* Only prints creation instructions if the file doesn't exist yet.
|
|
439
|
+
* If it exists, reminds Claude to update it if data models changed.
|
|
440
|
+
*/
|
|
441
|
+
function printServiceRecordingReminder(port) {
|
|
442
|
+
console.log(chalk.bold('Third-party services:'));
|
|
443
|
+
checkbox('When integrating any external service (auth, email, payments, storage, telemetry), update the tech stack');
|
|
444
|
+
console.log(chalk.dim(' Include the service name, URL, description, and envKeys (environment variable names it requires).'));
|
|
445
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" \\`));
|
|
446
|
+
console.log(chalk.dim(` -d '{"techStack": {"services": [{"name":"Stripe","url":"https://stripe.com","description":"Payments","envKeys":["STRIPE_SECRET_KEY","STRIPE_PUBLISHABLE_KEY"]}]}}'`));
|
|
447
|
+
console.log(chalk.dim(' This ensures the deploy roadmap tracks all services that need production credentials.'));
|
|
448
|
+
}
|
|
449
|
+
function printDataStructureInstructions() {
|
|
450
|
+
const root = getProjectRoot();
|
|
451
|
+
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
452
|
+
const exists = fs.existsSync(dsPath);
|
|
453
|
+
if (!exists) {
|
|
454
|
+
console.log(chalk.bold('Create the data structure config:'));
|
|
455
|
+
checkbox('Create `.codeyam/data-structure.json` describing all data sources');
|
|
456
|
+
console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
|
|
457
|
+
console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
|
|
458
|
+
console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
|
|
459
|
+
console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
|
|
460
|
+
console.log();
|
|
461
|
+
console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
|
|
462
|
+
console.log(chalk.dim(' "mock-api" for mocked external API responses'));
|
|
463
|
+
console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
|
|
464
|
+
console.log(chalk.dim(' Do NOT include types only used as component props.'));
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
|
|
468
|
+
console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
|
|
469
|
+
}
|
|
470
|
+
console.log();
|
|
471
|
+
}
|
|
206
472
|
/**
|
|
207
473
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
208
474
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -230,6 +496,10 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
230
496
|
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
231
497
|
}
|
|
232
498
|
console.log();
|
|
499
|
+
checkbox('Ensure every page file used in app scenarios has a glossary entry with its filePath:');
|
|
500
|
+
console.log(chalk.dim(' The register command validates pageFilePath against the glossary — add missing pages now.'));
|
|
501
|
+
console.log(chalk.dim(' Example: {"name":"CounterScreen","filePath":"app/(tabs)/index.tsx","type":"page","description":"Main counter page"}'));
|
|
502
|
+
console.log();
|
|
233
503
|
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
234
504
|
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
235
505
|
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
@@ -244,15 +514,24 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
244
514
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
245
515
|
}
|
|
246
516
|
else {
|
|
517
|
+
const appCtx = getTechStackContext(root);
|
|
247
518
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
519
|
+
if (appCtx.isExpo) {
|
|
520
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
521
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
522
|
+
}
|
|
248
523
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
249
524
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
250
525
|
}
|
|
251
|
-
|
|
252
|
-
console.log(chalk.
|
|
253
|
-
console.log(chalk.dim('
|
|
526
|
+
console.log();
|
|
527
|
+
console.log(chalk.bold('Register ALL scenarios at once (bulk registration):'));
|
|
528
|
+
console.log(chalk.dim(' Write an array of scenario objects to a temp file:'));
|
|
529
|
+
console.log(chalk.dim(' [{"name":"...","type":"application","url":"/","seed":{...}}, {"name":"...","url":"/other",...}]'));
|
|
530
|
+
console.log(chalk.dim(' Then register all at once: codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
531
|
+
console.log(chalk.yellow(' Bulk registration is preferred — faster and avoids repeated capture overhead.'));
|
|
532
|
+
console.log();
|
|
254
533
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
255
|
-
checkbox('After
|
|
534
|
+
checkbox('After registration, check the response for `clientErrors`');
|
|
256
535
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
257
536
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
258
537
|
}
|
|
@@ -288,6 +567,12 @@ function printExtractionPlanInstructions() {
|
|
|
288
567
|
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
289
568
|
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
290
569
|
console.log();
|
|
570
|
+
console.log(chalk.bold.red('THE DECOMPOSITION RULE: All code should do one thing.'));
|
|
571
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces to form a coordinated whole.'));
|
|
572
|
+
console.log(chalk.yellow(' Every opportunity to extract code into a sensible function, helper, or sub-component MUST be taken.'));
|
|
573
|
+
console.log(chalk.yellow(' Then extract AGAIN from the extracted code — keep going until each piece does one clearly defined thing.'));
|
|
574
|
+
console.log(chalk.yellow(' This applies to ALL code: backend routes, business logic, frontend components, utilities — everything.'));
|
|
575
|
+
console.log();
|
|
291
576
|
console.log(chalk.bold('Checklist:'));
|
|
292
577
|
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
293
578
|
checkbox('Read EVERY file used by this page/feature');
|
|
@@ -298,6 +583,12 @@ function printExtractionPlanInstructions() {
|
|
|
298
583
|
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
299
584
|
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
300
585
|
console.log(chalk.yellow(' Hooks: data fetching, state management, side effects'));
|
|
586
|
+
console.log(chalk.yellow(' Inline logic — backend AND frontend (MUST be extracted as named functions):'));
|
|
587
|
+
console.log(chalk.yellow(' conditionals/ternaries, computed/derived values, data transformations,'));
|
|
588
|
+
console.log(chalk.yellow(' string formatting/interpolation, array filtering/sorting/mapping callbacks,'));
|
|
589
|
+
console.log(chalk.yellow(' default value logic, null/undefined guards, date/number formatting,'));
|
|
590
|
+
console.log(chalk.yellow(' permission/role checks, status derivation (e.g. isOverdue(task)),'));
|
|
591
|
+
console.log(chalk.yellow(' request/response shaping, error message construction, config lookups'));
|
|
301
592
|
console.log();
|
|
302
593
|
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
303
594
|
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
@@ -308,6 +599,15 @@ function printExtractionPlanInstructions() {
|
|
|
308
599
|
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
309
600
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
310
601
|
console.log(chalk.yellow(' — Where it will go (new file path)'));
|
|
602
|
+
console.log(chalk.yellow(' — How you will test it (key inputs/outputs and edge cases)'));
|
|
603
|
+
console.log(chalk.yellow(' — What logic inside it can be further extracted as a separate testable function'));
|
|
604
|
+
console.log();
|
|
605
|
+
console.log(chalk.bold.red('BEFORE FINALIZING THE PLAN:'));
|
|
606
|
+
console.log(chalk.yellow(' Re-read every file ONE MORE TIME. For each piece of code, ask:'));
|
|
607
|
+
console.log(chalk.yellow(' 1. Does this do more than one thing? → Split it into pieces that each do one thing.'));
|
|
608
|
+
console.log(chalk.yellow(' 2. Is there logic here that could be its own function with clear inputs/outputs? → Extract it.'));
|
|
609
|
+
console.log(chalk.yellow(' 3. Can the extracted code itself be broken down further? → Keep extracting.'));
|
|
610
|
+
console.log(chalk.yellow(' If your plan has fewer functions than components, you probably missed extractable logic.'));
|
|
311
611
|
console.log();
|
|
312
612
|
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
313
613
|
}
|
|
@@ -315,10 +615,18 @@ function printExtractionPlanInstructions() {
|
|
|
315
615
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
316
616
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
317
617
|
*/
|
|
318
|
-
function printComponentCaptureInstructions() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
618
|
+
function printComponentCaptureInstructions(root) {
|
|
619
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
620
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
621
|
+
checkbox('Set up isolation routes: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
622
|
+
if (isExpo) {
|
|
623
|
+
console.log(chalk.dim(' This creates app/isolated-components/_layout.tsx (with __DEV__ guard).'));
|
|
624
|
+
console.log(chalk.dim(' You then create a flat .tsx file per component (NOT subdirectories — Expo Router uses flat files).'));
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with notFound() guard) and`));
|
|
628
|
+
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
629
|
+
}
|
|
322
630
|
checkbox('For each visual component:');
|
|
323
631
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
324
632
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -329,23 +637,37 @@ function printComponentCaptureInstructions() {
|
|
|
329
637
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
330
638
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
331
639
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
332
|
-
|
|
333
|
-
|
|
640
|
+
if (isExpo) {
|
|
641
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
642
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
646
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
647
|
+
}
|
|
334
648
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
335
649
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
650
|
+
if (isExpo) {
|
|
651
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
652
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
656
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
657
|
+
}
|
|
658
|
+
console.log(chalk.dim(isExpo
|
|
659
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
660
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
661
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
340
662
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
341
663
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
342
664
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
343
665
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
344
666
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
345
667
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
346
|
-
console.log(chalk.dim(` "url":"/
|
|
668
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
347
669
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
348
|
-
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/
|
|
670
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
349
671
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
350
672
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
351
673
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -353,6 +675,10 @@ function printComponentCaptureInstructions() {
|
|
|
353
675
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
354
676
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
355
677
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
678
|
+
console.log();
|
|
679
|
+
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
680
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
681
|
+
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
356
682
|
}
|
|
357
683
|
/**
|
|
358
684
|
* Print a section header.
|
|
@@ -393,9 +719,33 @@ function printProgressTracker(current) {
|
|
|
393
719
|
*
|
|
394
720
|
* Options:
|
|
395
721
|
* - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
|
|
722
|
+
* - feature: string → feature name for task directive
|
|
396
723
|
*/
|
|
397
724
|
function stopGate(current, opts) {
|
|
398
|
-
|
|
725
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
726
|
+
const currentLabel = STEP_LABELS[current] || `Step ${current}`;
|
|
727
|
+
const nextLabel = current < totalSteps ? STEP_LABELS[current + 1] : undefined;
|
|
728
|
+
console.log();
|
|
729
|
+
// ━━━ TASK ━━━ — deterministic task lifecycle directive
|
|
730
|
+
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
731
|
+
if (current >= 2) {
|
|
732
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
733
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
734
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
735
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
736
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
737
|
+
console.log();
|
|
738
|
+
let taskTitle;
|
|
739
|
+
if (current < totalSteps) {
|
|
740
|
+
taskTitle = `Complete codeyam editor step ${current}: '${currentLabel}' and move on to step ${current + 1}: '${nextLabel}'`;
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
taskTitle =
|
|
744
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
745
|
+
}
|
|
746
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
747
|
+
console.log();
|
|
748
|
+
}
|
|
399
749
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
400
750
|
console.log();
|
|
401
751
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -404,20 +754,12 @@ function stopGate(current, opts) {
|
|
|
404
754
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
405
755
|
}
|
|
406
756
|
console.log();
|
|
407
|
-
|
|
408
|
-
printProgressTracker(current);
|
|
409
|
-
console.log();
|
|
410
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
411
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
412
|
-
console.log();
|
|
413
|
-
if (current < 18) {
|
|
757
|
+
if (current < totalSteps) {
|
|
414
758
|
console.log(chalk.green('When done, run: ') +
|
|
415
759
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
416
760
|
}
|
|
417
761
|
else {
|
|
418
|
-
console.log(chalk.green('Feature complete!
|
|
419
|
-
chalk.bold('codeyam editor steps') +
|
|
420
|
-
chalk.green(' to start the next feature'));
|
|
762
|
+
console.log(chalk.green('Feature complete! Ask the user what to build next, then run: ') + chalk.bold('codeyam editor 1'));
|
|
421
763
|
}
|
|
422
764
|
console.log();
|
|
423
765
|
}
|
|
@@ -654,11 +996,17 @@ function printSetup(root) {
|
|
|
654
996
|
console.log();
|
|
655
997
|
// ── Design System ────────────────────────────────────────────────
|
|
656
998
|
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
657
|
-
console.log(chalk.dim(' Ask: "
|
|
999
|
+
console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
|
|
658
1000
|
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
659
|
-
console.log(
|
|
1001
|
+
console.log();
|
|
1002
|
+
for (const ds of DESIGN_SYSTEMS) {
|
|
1003
|
+
console.log(chalk.yellow(` Option label: "${ds.name}"`) +
|
|
1004
|
+
chalk.dim(` — ${ds.description}`));
|
|
1005
|
+
console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
|
|
1006
|
+
}
|
|
1007
|
+
console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
|
|
660
1008
|
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
661
|
-
console.log(chalk.yellow(' Option
|
|
1009
|
+
console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
|
|
662
1010
|
console.log(chalk.dim(' → Skip'));
|
|
663
1011
|
console.log();
|
|
664
1012
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -679,8 +1027,8 @@ function printSetup(root) {
|
|
|
679
1027
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
680
1028
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
681
1029
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
682
|
-
console.log(chalk.dim('
|
|
683
|
-
console.log(chalk.dim('
|
|
1030
|
+
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.").'));
|
|
1031
|
+
console.log(chalk.dim(' If MULTIPLE stacks match, use AskUserQuestion to let the user pick one.'));
|
|
684
1032
|
console.log();
|
|
685
1033
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
686
1034
|
for (const stack of TECH_STACKS) {
|
|
@@ -713,7 +1061,7 @@ function printSetup(root) {
|
|
|
713
1061
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
714
1062
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
715
1063
|
console.log();
|
|
716
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
1064
|
+
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).'));
|
|
717
1065
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
718
1066
|
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
719
1067
|
console.log();
|
|
@@ -785,9 +1133,7 @@ function printCycleOverview(root, state) {
|
|
|
785
1133
|
console.log();
|
|
786
1134
|
console.log(chalk.green('Continue with: ') +
|
|
787
1135
|
chalk.bold(`codeyam editor ${state.step}`));
|
|
788
|
-
console.log(chalk.dim('Or run ')
|
|
789
|
-
chalk.bold('codeyam editor 1') +
|
|
790
|
-
chalk.dim(' to start a new feature'));
|
|
1136
|
+
console.log(chalk.dim('Or ask the user what to build next, then run `codeyam editor 1` yourself (never expose this command to the user)'));
|
|
791
1137
|
console.log();
|
|
792
1138
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
793
1139
|
chalk.bold('codeyam editor change'));
|
|
@@ -815,7 +1161,8 @@ function printCycleOverview(root, state) {
|
|
|
815
1161
|
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
816
1162
|
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
817
1163
|
console.log();
|
|
818
|
-
console.log(chalk.green('Start now: ') +
|
|
1164
|
+
console.log(chalk.green('Start now: ') +
|
|
1165
|
+
'Ask the user what they want to build, then run `codeyam editor 1` (never expose this command to the user)');
|
|
819
1166
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
820
1167
|
}
|
|
821
1168
|
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
@@ -873,6 +1220,15 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
873
1220
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
874
1221
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
875
1222
|
console.log();
|
|
1223
|
+
checkbox('Detect and set the project tech stack:');
|
|
1224
|
+
console.log(chalk.dim(' Scan package.json, framework configs (.env, .env.example, tsconfig.json, next.config.*, prisma/schema.prisma, etc.)'));
|
|
1225
|
+
console.log(chalk.dim(' to detect languages, frameworks, databases, services, libraries, and infrastructure.'));
|
|
1226
|
+
console.log(chalk.dim(' Each item MUST have name, url, and description. version and envKeys are optional.'));
|
|
1227
|
+
console.log(chalk.dim(` curl -s -X POST http://localhost:${port}/api/editor-project-info \\`));
|
|
1228
|
+
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
1229
|
+
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":[]}}\''));
|
|
1230
|
+
console.log(chalk.dim(' Replace the example values with what you detect in this project. Omit empty categories.'));
|
|
1231
|
+
console.log();
|
|
876
1232
|
const designSystem = readDesignSystem(root);
|
|
877
1233
|
if (designSystem) {
|
|
878
1234
|
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
@@ -886,6 +1242,8 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
886
1242
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
887
1243
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
888
1244
|
console.log();
|
|
1245
|
+
checkboxHandoff();
|
|
1246
|
+
console.log();
|
|
889
1247
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
890
1248
|
console.log();
|
|
891
1249
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
@@ -894,12 +1252,6 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
894
1252
|
console.log();
|
|
895
1253
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
896
1254
|
console.log();
|
|
897
|
-
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
898
|
-
printProgressTracker(1);
|
|
899
|
-
console.log();
|
|
900
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
901
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
902
|
-
console.log();
|
|
903
1255
|
console.log(chalk.green('When done, run: ') +
|
|
904
1256
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
905
1257
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
@@ -927,42 +1279,82 @@ function printStep2(root, feature) {
|
|
|
927
1279
|
if (isResuming) {
|
|
928
1280
|
printResumptionHeader(2);
|
|
929
1281
|
}
|
|
930
|
-
console.log(chalk.bold.red('━━━ MANDATORY: CREATE TASK NOW ━━━'));
|
|
931
|
-
console.log();
|
|
932
|
-
console.log(chalk.red(`Run TaskCreate with title: "Use \`codeyam editor\` to work on ${feature}"`));
|
|
933
|
-
console.log(chalk.red('DO NOT read files, scaffold, write code, or do ANYTHING else until this task exists.'));
|
|
934
|
-
console.log(chalk.red('This is not optional. Create the task, then continue below.'));
|
|
935
|
-
console.log(chalk.bold.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
936
|
-
console.log();
|
|
937
1282
|
console.log('Get the project ready to build.');
|
|
938
1283
|
console.log();
|
|
1284
|
+
const ctx = getTechStackContext(root);
|
|
939
1285
|
// If no project exists yet, include scaffolding instructions first
|
|
940
1286
|
if (!projectExists) {
|
|
941
1287
|
console.log(chalk.bold('Scaffold the project:'));
|
|
942
1288
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
943
|
-
|
|
1289
|
+
if (ctx.isExpo) {
|
|
1290
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1291
|
+
}
|
|
1292
|
+
else if (ctx.isChromeExt) {
|
|
1293
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1294
|
+
}
|
|
1295
|
+
else {
|
|
1296
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1297
|
+
}
|
|
944
1298
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
945
1299
|
console.log();
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1300
|
+
if (ctx.isExpo) {
|
|
1301
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1302
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1303
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1304
|
+
console.log();
|
|
1305
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1306
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1307
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1308
|
+
console.log();
|
|
1309
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1310
|
+
console.log();
|
|
1311
|
+
checkbox('Ask the user what to name the app (use AskUserQuestion), then update app.json');
|
|
1312
|
+
console.log(chalk.dim(' Update name, slug, scheme, ios.bundleIdentifier (com.codeyam.<slug>), and android.package'));
|
|
1313
|
+
console.log(chalk.dim(' Also set the projectTitle via: curl -s -X POST http://localhost:' +
|
|
1314
|
+
port +
|
|
1315
|
+
'/api/editor-project-info -H "Content-Type: application/json" -d \'{"projectTitle":"<name>"}\''));
|
|
1316
|
+
console.log();
|
|
1317
|
+
checkbox('Generate an app icon using `sharp`');
|
|
1318
|
+
console.log(chalk.dim(' Create `scripts/generate-icon.mjs` and `mkdir -p assets`'));
|
|
1319
|
+
console.log(chalk.yellow(' ICON DESIGN GOAL: The icon must look distinctive on a home screen full of other app icons.'));
|
|
1320
|
+
console.log(chalk.yellow(' Use multiple bold colors from the design system and complex, overlapping shapes.'));
|
|
1321
|
+
console.log(chalk.yellow(' NO simple/minimal icons — no single glyph on a flat background, no plain text, no basic circles.'));
|
|
1322
|
+
console.log(chalk.yellow(' Make it colorful, layered, and dense. Use gradients, multiple contrasting fills, and depth.'));
|
|
1323
|
+
console.log(chalk.dim(' Use sharp to render SVG to 1024x1024 PNG. iOS: flatten onto background (no alpha channel).'));
|
|
1324
|
+
console.log(chalk.dim(' Generate: icon.png, adaptive-icon.png, favicon.png (48x48). Run `node scripts/generate-icon.mjs`'));
|
|
1325
|
+
console.log();
|
|
1326
|
+
}
|
|
1327
|
+
else if (ctx.isChromeExt) {
|
|
1328
|
+
// Chrome Extension: use chrome.storage
|
|
1329
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1330
|
+
console.log();
|
|
1331
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1332
|
+
console.log();
|
|
1333
|
+
}
|
|
1334
|
+
else {
|
|
1335
|
+
// Next.js: Prisma + database
|
|
1336
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1337
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1338
|
+
console.log();
|
|
1339
|
+
checkbox('Push schema and seed the database');
|
|
1340
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1341
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1342
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1343
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1344
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1345
|
+
console.log();
|
|
1346
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1347
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1348
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1349
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1350
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1351
|
+
console.log();
|
|
1352
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1353
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1354
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1355
|
+
console.log();
|
|
1356
|
+
}
|
|
1357
|
+
printDataStructureInstructions();
|
|
966
1358
|
}
|
|
967
1359
|
else {
|
|
968
1360
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -977,7 +1369,12 @@ function printStep2(root, feature) {
|
|
|
977
1369
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
978
1370
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
979
1371
|
console.log();
|
|
1372
|
+
printDataStructureInstructions();
|
|
980
1373
|
}
|
|
1374
|
+
console.log();
|
|
1375
|
+
printServiceRecordingReminder(port);
|
|
1376
|
+
console.log();
|
|
1377
|
+
checkboxHandoff();
|
|
981
1378
|
stopGate(2);
|
|
982
1379
|
}
|
|
983
1380
|
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
@@ -1002,19 +1399,42 @@ function printStep3(root, feature) {
|
|
|
1002
1399
|
if (isResuming) {
|
|
1003
1400
|
printResumptionHeader(3);
|
|
1004
1401
|
}
|
|
1402
|
+
const ctx = getTechStackContext(root);
|
|
1005
1403
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1006
1404
|
console.log();
|
|
1007
1405
|
console.log(chalk.bold('Checklist:'));
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1406
|
+
if (ctx.isExpo) {
|
|
1407
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1408
|
+
if (!projectExists) {
|
|
1409
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1410
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1411
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1412
|
+
console.log();
|
|
1413
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1414
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1415
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1416
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1421
|
+
if (!projectExists) {
|
|
1422
|
+
checkbox('Seed the database with demo data');
|
|
1423
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1424
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1425
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1426
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1427
|
+
console.log();
|
|
1428
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1429
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1430
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1431
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1432
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1433
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1434
|
+
}
|
|
1015
1435
|
}
|
|
1016
1436
|
checkbox('Verify the dev server shows the changes');
|
|
1017
|
-
checkbox(
|
|
1437
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1018
1438
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1019
1439
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1020
1440
|
console.log();
|
|
@@ -1031,16 +1451,33 @@ function printStep3(root, feature) {
|
|
|
1031
1451
|
console.log();
|
|
1032
1452
|
console.log(designSystem);
|
|
1033
1453
|
console.log();
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1454
|
+
if (ctx.isExpo) {
|
|
1455
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1456
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1457
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1458
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1459
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1460
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1461
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1462
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1463
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1464
|
+
checkbox('Buttons: put backgroundColor, borderRadius, borderStyle on a wrapping <View>, NOT on <Pressable>');
|
|
1465
|
+
console.log(chalk.dim(' Pressable renders these styles on web but FAILS SILENTLY on native devices.'));
|
|
1466
|
+
console.log(chalk.dim(' Use a <View> with overflow:"hidden" for the visual shell, <Pressable> inside for touch only.'));
|
|
1467
|
+
console.log(chalk.dim(' For press feedback: use onPressIn/onPressOut + useState to toggle opacity, not function-style style.'));
|
|
1468
|
+
}
|
|
1469
|
+
else {
|
|
1470
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1471
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1472
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1473
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1474
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1475
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1476
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1477
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1478
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1479
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1480
|
+
}
|
|
1044
1481
|
}
|
|
1045
1482
|
console.log();
|
|
1046
1483
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1051,7 +1488,12 @@ function printStep3(root, feature) {
|
|
|
1051
1488
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
1052
1489
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
1053
1490
|
printDimensionGuidance(dim, dimNames);
|
|
1491
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1492
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
1054
1493
|
console.log();
|
|
1494
|
+
printServiceRecordingReminder(port);
|
|
1495
|
+
console.log();
|
|
1496
|
+
checkboxHandoff();
|
|
1055
1497
|
stopGate(3);
|
|
1056
1498
|
}
|
|
1057
1499
|
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
@@ -1066,6 +1508,8 @@ function printStep4(root, feature) {
|
|
|
1066
1508
|
label: STEP_LABELS[4],
|
|
1067
1509
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1068
1510
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1511
|
+
appFormats: prevState?.appFormats,
|
|
1512
|
+
techStackId: prevState?.techStackId,
|
|
1069
1513
|
});
|
|
1070
1514
|
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1071
1515
|
stepHeader(4, 'Verify Prototype', feature);
|
|
@@ -1075,14 +1519,13 @@ function printStep4(root, feature) {
|
|
|
1075
1519
|
console.log('Verify everything works before presenting the prototype.');
|
|
1076
1520
|
console.log();
|
|
1077
1521
|
console.log(chalk.bold('Verify the dev server:'));
|
|
1078
|
-
console.log(chalk.dim(
|
|
1079
|
-
console.log(chalk.dim(
|
|
1080
|
-
console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
|
|
1522
|
+
console.log(chalk.dim(' # Verify pages and API routes load:'));
|
|
1523
|
+
console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
|
|
1081
1524
|
console.log();
|
|
1082
1525
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1083
1526
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
1084
|
-
checkbox('Verify
|
|
1085
|
-
|
|
1527
|
+
checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1528
|
+
console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
|
|
1086
1529
|
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1087
1530
|
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
1088
1531
|
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
@@ -1093,6 +1536,14 @@ function printStep4(root, feature) {
|
|
|
1093
1536
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1094
1537
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1095
1538
|
console.log();
|
|
1539
|
+
const ctx4 = getTechStackContext(root);
|
|
1540
|
+
if (ctx4.isExpo) {
|
|
1541
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1542
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1543
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1544
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1545
|
+
console.log();
|
|
1546
|
+
}
|
|
1096
1547
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1097
1548
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1098
1549
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1100,6 +1551,8 @@ function printStep4(root, feature) {
|
|
|
1100
1551
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1101
1552
|
console.log();
|
|
1102
1553
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1554
|
+
console.log();
|
|
1555
|
+
checkboxHandoff();
|
|
1103
1556
|
stopGate(4);
|
|
1104
1557
|
}
|
|
1105
1558
|
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
@@ -1115,6 +1568,8 @@ function printStep5(root, feature) {
|
|
|
1115
1568
|
label: STEP_LABELS[5],
|
|
1116
1569
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1117
1570
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1571
|
+
appFormats: prevState?.appFormats,
|
|
1572
|
+
techStackId: prevState?.techStackId,
|
|
1118
1573
|
});
|
|
1119
1574
|
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1120
1575
|
stepHeader(5, 'Confirm', feature);
|
|
@@ -1125,7 +1580,7 @@ function printStep5(root, feature) {
|
|
|
1125
1580
|
console.log();
|
|
1126
1581
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
1127
1582
|
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
1128
|
-
checkbox('Verify
|
|
1583
|
+
checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1129
1584
|
console.log();
|
|
1130
1585
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
1131
1586
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
@@ -1147,6 +1602,8 @@ function printStep5(root, feature) {
|
|
|
1147
1602
|
printDimensionGuidance(dim, dimNames);
|
|
1148
1603
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1149
1604
|
console.log(chalk.dim(' If you modified an existing page, refresh the preview to show the changes.'));
|
|
1605
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
1606
|
+
console.log(chalk.red(' The preview only updates when you explicitly call `codeyam editor preview`. Verify, then describe.'));
|
|
1150
1607
|
console.log();
|
|
1151
1608
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1152
1609
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1159,6 +1616,8 @@ function printStep5(root, feature) {
|
|
|
1159
1616
|
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1160
1617
|
console.log();
|
|
1161
1618
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1619
|
+
console.log();
|
|
1620
|
+
checkboxHandoff();
|
|
1162
1621
|
stopGate(5, { confirm: true });
|
|
1163
1622
|
}
|
|
1164
1623
|
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
@@ -1172,6 +1631,8 @@ function printStep6(root, feature) {
|
|
|
1172
1631
|
label: STEP_LABELS[6],
|
|
1173
1632
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1174
1633
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1634
|
+
appFormats: prevState?.appFormats,
|
|
1635
|
+
techStackId: prevState?.techStackId,
|
|
1175
1636
|
});
|
|
1176
1637
|
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1177
1638
|
stepHeader(6, 'Deconstruct', feature);
|
|
@@ -1182,6 +1643,8 @@ function printStep6(root, feature) {
|
|
|
1182
1643
|
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1183
1644
|
console.log();
|
|
1184
1645
|
printExtractionPlanInstructions();
|
|
1646
|
+
console.log();
|
|
1647
|
+
checkboxHandoff();
|
|
1185
1648
|
stopGate(6);
|
|
1186
1649
|
}
|
|
1187
1650
|
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
@@ -1197,12 +1660,15 @@ function printStep7(root, feature) {
|
|
|
1197
1660
|
label: STEP_LABELS[7],
|
|
1198
1661
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1199
1662
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1663
|
+
appFormats: prevState?.appFormats,
|
|
1664
|
+
techStackId: prevState?.techStackId,
|
|
1200
1665
|
});
|
|
1201
1666
|
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1202
1667
|
stepHeader(7, 'Extract', feature);
|
|
1203
1668
|
if (isResuming) {
|
|
1204
1669
|
printResumptionHeader(7);
|
|
1205
1670
|
}
|
|
1671
|
+
const ctx = getTechStackContext(root);
|
|
1206
1672
|
console.log('Execute your extraction plan from step 6.');
|
|
1207
1673
|
console.log();
|
|
1208
1674
|
console.log(chalk.bold('Components:'));
|
|
@@ -1215,19 +1681,45 @@ function printStep7(root, feature) {
|
|
|
1215
1681
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1216
1682
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1217
1683
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1684
|
+
console.log(chalk.dim(' EVERY conditional branch in the function MUST have a test that exercises it'));
|
|
1685
|
+
console.log(chalk.dim(' EVERY return path MUST be tested — if a function can return 3 different things, write 3+ tests'));
|
|
1686
|
+
console.log(chalk.dim(' Boundary values: 0, 1, -1, empty string, empty array, undefined, null'));
|
|
1218
1687
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1219
|
-
|
|
1688
|
+
console.log(chalk.dim(' Wrap all tests in a describe("FunctionName", ...) block — the audit matches on this name'));
|
|
1689
|
+
if (ctx.isExpo) {
|
|
1690
|
+
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`');
|
|
1691
|
+
}
|
|
1692
|
+
else {
|
|
1693
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1694
|
+
}
|
|
1220
1695
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1221
1696
|
console.log();
|
|
1222
1697
|
console.log(chalk.bold('Recursive pass:'));
|
|
1223
1698
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1224
1699
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1225
|
-
console.log(chalk.yellow(
|
|
1700
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1226
1701
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1227
1702
|
console.log();
|
|
1703
|
+
console.log(chalk.bold('Decomposition pass (backend AND frontend):'));
|
|
1704
|
+
checkbox('Re-read EVERY file you created or modified. For each, extract a function/helper/sub-component for:');
|
|
1705
|
+
console.log(chalk.yellow(' — Conditionals (e.g. `isOverdue ? "red" : "green"` → `getStatusColor(date)`)'));
|
|
1706
|
+
console.log(chalk.yellow(' — Computed values (e.g. `items.filter(...).length` → `countActiveItems(items)`)'));
|
|
1707
|
+
console.log(chalk.yellow(' — Formatting (e.g. `${price.toFixed(2)}` → `formatPrice(price)`)'));
|
|
1708
|
+
console.log(chalk.yellow(' — Data transforms, validation, request/response shaping'));
|
|
1709
|
+
console.log(chalk.yellow(' — Anything doing more than one thing → split until each piece does one clearly defined thing'));
|
|
1710
|
+
checkbox('Then look at what you just extracted — can IT be broken down further? Keep going.');
|
|
1711
|
+
checkbox('Write tests for every extracted function (same TDD: failing tests FIRST)');
|
|
1712
|
+
console.log();
|
|
1713
|
+
console.log(chalk.bold.red('TEST QUALITY SELF-CHECK (before proceeding):'));
|
|
1714
|
+
checkbox('For EACH test file: count the test cases. Fewer than 3 tests for any function is a red flag.');
|
|
1715
|
+
checkbox('For EACH tested function: read the function, count the conditional branches. Every branch MUST have a test.');
|
|
1716
|
+
checkbox('For EACH tested function: verify you test at least one error/edge case, not just happy paths.');
|
|
1717
|
+
console.log(chalk.yellow(' If a function has an if/else, switch, or ternary — each path needs its own test case.'));
|
|
1718
|
+
console.log(chalk.yellow(' If a function handles null/undefined/empty — test those inputs explicitly.'));
|
|
1719
|
+
console.log();
|
|
1228
1720
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1229
1721
|
checkbox('Run all tests and verify they pass');
|
|
1230
|
-
checkbox(
|
|
1722
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1231
1723
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1232
1724
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1233
1725
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1235,6 +1727,8 @@ function printStep7(root, feature) {
|
|
|
1235
1727
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1236
1728
|
console.log();
|
|
1237
1729
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1730
|
+
console.log();
|
|
1731
|
+
checkboxHandoff();
|
|
1238
1732
|
stopGate(7);
|
|
1239
1733
|
}
|
|
1240
1734
|
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
@@ -1248,6 +1742,8 @@ function printStep8(root, feature) {
|
|
|
1248
1742
|
label: STEP_LABELS[8],
|
|
1249
1743
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1250
1744
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1745
|
+
appFormats: prevState?.appFormats,
|
|
1746
|
+
techStackId: prevState?.techStackId,
|
|
1251
1747
|
});
|
|
1252
1748
|
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1253
1749
|
stepHeader(8, 'Glossary', feature);
|
|
@@ -1274,6 +1770,8 @@ function printStep9(root, feature) {
|
|
|
1274
1770
|
label: STEP_LABELS[9],
|
|
1275
1771
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1276
1772
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1773
|
+
appFormats: prevState?.appFormats,
|
|
1774
|
+
techStackId: prevState?.techStackId,
|
|
1277
1775
|
});
|
|
1278
1776
|
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1279
1777
|
stepHeader(9, 'Analyze and Verify', feature);
|
|
@@ -1288,19 +1786,35 @@ function printStep9(root, feature) {
|
|
|
1288
1786
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1289
1787
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1290
1788
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1291
|
-
|
|
1789
|
+
const ctx9 = getTechStackContext(root);
|
|
1790
|
+
printComponentCaptureInstructions(root);
|
|
1292
1791
|
console.log();
|
|
1293
1792
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1294
1793
|
checkbox('Run ALL test files created in step 7');
|
|
1295
|
-
console.log(chalk.dim(
|
|
1794
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1296
1795
|
checkbox('Verify every test passes');
|
|
1297
1796
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1298
1797
|
console.log();
|
|
1299
1798
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1300
1799
|
console.log();
|
|
1800
|
+
console.log(chalk.bold.red('DECOMPOSITION & COVERAGE REVIEW (before running audit):'));
|
|
1801
|
+
checkbox('Re-read every file. Extract any logic that could be its own function/helper/sub-component.');
|
|
1802
|
+
console.log(chalk.yellow(' Code either contains logic OR brings together smaller pieces.'));
|
|
1803
|
+
console.log(chalk.yellow(' Keep extracting until each piece does one clearly defined thing.'));
|
|
1804
|
+
console.log(chalk.yellow(' For each extraction: write failing test first, then extract, then make test pass.'));
|
|
1805
|
+
checkbox('Open each test file and verify: ≥3 test cases per function, happy path + edge case + error case');
|
|
1806
|
+
checkbox('If any function has fewer tests than conditional branches, add the missing tests NOW');
|
|
1807
|
+
checkbox('Update `.codeyam/glossary.json` with any new functions before running audit');
|
|
1808
|
+
console.log();
|
|
1301
1809
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1302
1810
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1303
|
-
console.log(chalk.dim('
|
|
1811
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1812
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1813
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1814
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1815
|
+
console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
|
|
1816
|
+
console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
|
|
1817
|
+
console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
|
|
1304
1818
|
console.log();
|
|
1305
1819
|
stopGate(9);
|
|
1306
1820
|
}
|
|
@@ -1317,6 +1831,8 @@ function printStep10(root, feature) {
|
|
|
1317
1831
|
label: STEP_LABELS[10],
|
|
1318
1832
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1319
1833
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1834
|
+
appFormats: prevState?.appFormats,
|
|
1835
|
+
techStackId: prevState?.techStackId,
|
|
1320
1836
|
});
|
|
1321
1837
|
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1322
1838
|
stepHeader(10, 'App Scenarios', feature);
|
|
@@ -1335,6 +1851,7 @@ function printStep10(root, feature) {
|
|
|
1335
1851
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1336
1852
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1337
1853
|
console.log(chalk.cyan(' • Make content diverse enough that edge cases surface naturally'));
|
|
1854
|
+
console.log(chalk.cyan(' • Use real image URLs (Unsplash, Pravatar) — not broken placeholders or empty strings'));
|
|
1338
1855
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1339
1856
|
console.log();
|
|
1340
1857
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1380,6 +1897,8 @@ function printStep11(root, feature) {
|
|
|
1380
1897
|
label: STEP_LABELS[11],
|
|
1381
1898
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1382
1899
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1900
|
+
appFormats: prevState?.appFormats,
|
|
1901
|
+
techStackId: prevState?.techStackId,
|
|
1383
1902
|
});
|
|
1384
1903
|
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1385
1904
|
stepHeader(11, 'User Scenarios', feature);
|
|
@@ -1426,6 +1945,8 @@ function printStep12(root, feature) {
|
|
|
1426
1945
|
label: STEP_LABELS[12],
|
|
1427
1946
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1428
1947
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1948
|
+
appFormats: prevState?.appFormats,
|
|
1949
|
+
techStackId: prevState?.techStackId,
|
|
1429
1950
|
});
|
|
1430
1951
|
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1431
1952
|
stepHeader(12, 'Verify', feature);
|
|
@@ -1472,6 +1993,8 @@ function printStep13(root, feature) {
|
|
|
1472
1993
|
label: STEP_LABELS[13],
|
|
1473
1994
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1474
1995
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1996
|
+
appFormats: prevState?.appFormats,
|
|
1997
|
+
techStackId: prevState?.techStackId,
|
|
1475
1998
|
});
|
|
1476
1999
|
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
1477
2000
|
stepHeader(13, 'Journal', feature);
|
|
@@ -1505,6 +2028,8 @@ function printStep14(root, feature) {
|
|
|
1505
2028
|
label: STEP_LABELS[14],
|
|
1506
2029
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1507
2030
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2031
|
+
appFormats: prevState?.appFormats,
|
|
2032
|
+
techStackId: prevState?.techStackId,
|
|
1508
2033
|
});
|
|
1509
2034
|
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
1510
2035
|
stepHeader(14, 'Review', feature);
|
|
@@ -1524,6 +2049,8 @@ function printStep14(root, feature) {
|
|
|
1524
2049
|
checkbox('Fix or remove any broken images before continuing');
|
|
1525
2050
|
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1526
2051
|
checkbox('Run `codeyam editor audit` to verify completeness of scenarios and tests');
|
|
2052
|
+
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`');
|
|
2053
|
+
console.log(chalk.dim(` Update via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"techStack": {...}}'`));
|
|
1527
2054
|
checkbox('Do not proceed until all checks pass');
|
|
1528
2055
|
stopGate(14);
|
|
1529
2056
|
}
|
|
@@ -1538,6 +2065,8 @@ function printStep15(root, feature) {
|
|
|
1538
2065
|
label: STEP_LABELS[15],
|
|
1539
2066
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1540
2067
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2068
|
+
appFormats: prevState?.appFormats,
|
|
2069
|
+
techStackId: prevState?.techStackId,
|
|
1541
2070
|
});
|
|
1542
2071
|
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
1543
2072
|
stepHeader(15, 'Present', feature);
|
|
@@ -1549,7 +2078,7 @@ function printStep15(root, feature) {
|
|
|
1549
2078
|
console.log(chalk.bold('Checklist:'));
|
|
1550
2079
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1551
2080
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
1552
|
-
console.log(chalk.dim(' The
|
|
2081
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1553
2082
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1554
2083
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1555
2084
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1766,7 +2295,7 @@ function printMigrateStep3(root) {
|
|
|
1766
2295
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1767
2296
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1768
2297
|
console.log();
|
|
1769
|
-
printComponentCaptureInstructions();
|
|
2298
|
+
printComponentCaptureInstructions(root);
|
|
1770
2299
|
console.log();
|
|
1771
2300
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1772
2301
|
}
|
|
@@ -1789,7 +2318,7 @@ function printMigrateStep4(root) {
|
|
|
1789
2318
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1790
2319
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1791
2320
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1792
|
-
console.log(chalk.dim(' The
|
|
2321
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1793
2322
|
console.log();
|
|
1794
2323
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1795
2324
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1901,7 +2430,7 @@ function printMigrateStep8(root) {
|
|
|
1901
2430
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1902
2431
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1903
2432
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1904
|
-
checkbox('Find all scenarios that use the real page route (not /
|
|
2433
|
+
checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
|
|
1905
2434
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1906
2435
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1907
2436
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1909,10 +2438,10 @@ function printMigrateStep8(root) {
|
|
|
1909
2438
|
console.log();
|
|
1910
2439
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1911
2440
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1912
|
-
printComponentCaptureInstructions();
|
|
2441
|
+
printComponentCaptureInstructions(root);
|
|
1913
2442
|
console.log();
|
|
1914
2443
|
console.log(chalk.bold('Verify:'));
|
|
1915
|
-
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
2444
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
1916
2445
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1917
2446
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1918
2447
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -1956,7 +2485,7 @@ function printMigrateStep10(root) {
|
|
|
1956
2485
|
console.log();
|
|
1957
2486
|
console.log(chalk.bold('Checklist:'));
|
|
1958
2487
|
checkbox('Show results: `codeyam editor show-results`');
|
|
1959
|
-
console.log(chalk.dim(' The
|
|
2488
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1960
2489
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1961
2490
|
console.log();
|
|
1962
2491
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2187,6 +2716,8 @@ function printStep16(root, feature) {
|
|
|
2187
2716
|
label: STEP_LABELS[16],
|
|
2188
2717
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2189
2718
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2719
|
+
appFormats: prevState?.appFormats,
|
|
2720
|
+
techStackId: prevState?.techStackId,
|
|
2190
2721
|
});
|
|
2191
2722
|
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2192
2723
|
stepHeader(16, 'Commit', feature);
|
|
@@ -2213,6 +2744,8 @@ function printStep17(root, feature) {
|
|
|
2213
2744
|
label: STEP_LABELS[17],
|
|
2214
2745
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2215
2746
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2747
|
+
appFormats: prevState?.appFormats,
|
|
2748
|
+
techStackId: prevState?.techStackId,
|
|
2216
2749
|
});
|
|
2217
2750
|
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2218
2751
|
stepHeader(17, 'Finalize', feature);
|
|
@@ -2238,6 +2771,8 @@ function printStep18(root, feature) {
|
|
|
2238
2771
|
label: STEP_LABELS[18],
|
|
2239
2772
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2240
2773
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2774
|
+
appFormats: prevState?.appFormats,
|
|
2775
|
+
techStackId: prevState?.techStackId,
|
|
2241
2776
|
});
|
|
2242
2777
|
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2243
2778
|
stepHeader(18, 'Push', feature);
|
|
@@ -2253,15 +2788,13 @@ function printStep18(root, feature) {
|
|
|
2253
2788
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2254
2789
|
console.log(chalk.dim(' If NO remote exists, ask (AskUserQuestion): "This project doesn\'t have a git remote yet. Would you like help setting one up? I can walk you through creating a GitHub repository." with options "Yes, set up remote" and "No, skip"'));
|
|
2255
2790
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2256
|
-
|
|
2257
|
-
checkbox('Create a new task with EXACTLY this title: "Ask the user what to build next, then run `codeyam editor 1` to start the next feature"');
|
|
2258
|
-
console.log(chalk.dim(' There must ALWAYS be an active task. Never leave the task list empty.'));
|
|
2259
|
-
console.log(chalk.dim(' This task reminds you to use the editor workflow for the next feature.'));
|
|
2791
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2260
2792
|
console.log();
|
|
2261
|
-
|
|
2262
|
-
console.log(chalk.yellow('
|
|
2793
|
+
const port = getServerPort();
|
|
2794
|
+
console.log(chalk.bold.yellow('IMPORTANT: After the push succeeds (or the user skips it), the feature is DONE.'));
|
|
2795
|
+
console.log(chalk.yellow(` Signal the UI by running: curl -s -X POST http://localhost:${port}/api/editor-feature-complete`));
|
|
2796
|
+
console.log(chalk.yellow(' Then STOP and wait. Do NOT ask the user what to build next — the UI handles the transition.'));
|
|
2263
2797
|
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2264
|
-
console.log(chalk.yellow(' The next feature MUST start with `codeyam editor 1`.'));
|
|
2265
2798
|
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2266
2799
|
stopGate(18, { confirm: true });
|
|
2267
2800
|
}
|
|
@@ -2292,10 +2825,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2292
2825
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2293
2826
|
}
|
|
2294
2827
|
catch {
|
|
2828
|
+
if (options.silent)
|
|
2829
|
+
return;
|
|
2295
2830
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2296
2831
|
process.exit(1);
|
|
2297
2832
|
}
|
|
2298
2833
|
if (glossaryEntries.length === 0) {
|
|
2834
|
+
if (options.silent)
|
|
2835
|
+
return;
|
|
2299
2836
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2300
2837
|
process.exit(1);
|
|
2301
2838
|
}
|
|
@@ -2337,25 +2874,137 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2337
2874
|
// Non-fatal — scenario file paths just won't be included
|
|
2338
2875
|
}
|
|
2339
2876
|
const progress = new ProgressReporter();
|
|
2340
|
-
//
|
|
2877
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2878
|
+
// ~120 files when only 2 are incomplete.
|
|
2879
|
+
let targetFilePaths = filePaths;
|
|
2880
|
+
try {
|
|
2881
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2882
|
+
const db = getDatabase();
|
|
2883
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2884
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2885
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2886
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2887
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2888
|
+
if (!options.silent) {
|
|
2889
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2890
|
+
}
|
|
2891
|
+
targetFilePaths = incomplete;
|
|
2892
|
+
}
|
|
2893
|
+
else if (incomplete.length === 0) {
|
|
2894
|
+
if (!options.silent) {
|
|
2895
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2896
|
+
}
|
|
2897
|
+
// Still need to run the import graph + backfill below
|
|
2898
|
+
targetFilePaths = [];
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
catch {
|
|
2902
|
+
// Non-fatal — fall back to analyzing all files
|
|
2903
|
+
}
|
|
2904
|
+
// If specific file paths were requested, scope analysis to only those files.
|
|
2905
|
+
// The full filePaths set is still used for the import graph below.
|
|
2906
|
+
if (options.filePaths && options.filePaths.length > 0) {
|
|
2907
|
+
const requested = new Set(options.filePaths);
|
|
2908
|
+
targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
|
|
2909
|
+
if (targetFilePaths.length === 0 && !options.silent) {
|
|
2910
|
+
console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2341
2914
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2342
2915
|
// The analyzer will discover and create them from file paths.
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2916
|
+
if (targetFilePaths.length > 0) {
|
|
2917
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2918
|
+
try {
|
|
2919
|
+
await runAnalysisForEntities({
|
|
2920
|
+
projectRoot: root,
|
|
2921
|
+
filePaths: targetFilePaths,
|
|
2922
|
+
progress,
|
|
2923
|
+
onlyDataStructure: true,
|
|
2924
|
+
});
|
|
2925
|
+
}
|
|
2926
|
+
catch (err) {
|
|
2927
|
+
progress.fail('Analysis failed');
|
|
2928
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2929
|
+
if (options.silent) {
|
|
2930
|
+
// Internal caller — don't kill the process, let the caller handle it
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2934
|
+
process.exit(1);
|
|
2935
|
+
}
|
|
2936
|
+
progress.succeed('Analysis complete');
|
|
2351
2937
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2938
|
+
// Surface analysis errors if any occurred
|
|
2939
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2940
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2941
|
+
if (!options.silent) {
|
|
2942
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2943
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2944
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2945
|
+
}
|
|
2946
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2947
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2948
|
+
// with the files we tried to analyze.
|
|
2949
|
+
try {
|
|
2950
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2951
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2952
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2953
|
+
// Parse error messages to identify failed file paths.
|
|
2954
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2955
|
+
// or more generic "CodeYam Error: <message>"
|
|
2956
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2957
|
+
const failedEntityNames = new Set();
|
|
2958
|
+
let match;
|
|
2959
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2960
|
+
const name = match[1] || match[2];
|
|
2961
|
+
if (name)
|
|
2962
|
+
failedEntityNames.add(name);
|
|
2963
|
+
}
|
|
2964
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2965
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2966
|
+
let glossary = [];
|
|
2967
|
+
try {
|
|
2968
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2969
|
+
}
|
|
2970
|
+
catch {
|
|
2971
|
+
// Non-fatal
|
|
2972
|
+
}
|
|
2973
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2974
|
+
const now = new Date().toISOString();
|
|
2975
|
+
const updatedFailures = { ...existingFailures };
|
|
2976
|
+
if (failedEntityNames.size > 0) {
|
|
2977
|
+
for (const name of failedEntityNames) {
|
|
2978
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2979
|
+
if (entry) {
|
|
2980
|
+
updatedFailures[entry.filePath] = {
|
|
2981
|
+
entityName: name,
|
|
2982
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2983
|
+
failedAt: now,
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
// When we can't parse specific entity names from the error, DON'T mark
|
|
2989
|
+
// all target files as failed. The error may be non-fatal (e.g., a cache
|
|
2990
|
+
// miss logged as "CodeYam Error") and blanket-marking every file as
|
|
2991
|
+
// permanently failed blocks future audits from resolving them.
|
|
2992
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2993
|
+
}
|
|
2994
|
+
catch {
|
|
2995
|
+
// Non-fatal — failure tracking is best-effort
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
else {
|
|
2999
|
+
// No errors — clear any stale failure tracking
|
|
3000
|
+
try {
|
|
3001
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
3002
|
+
writeAnalysisFailures(root, {});
|
|
3003
|
+
}
|
|
3004
|
+
catch {
|
|
3005
|
+
// Non-fatal
|
|
3006
|
+
}
|
|
2357
3007
|
}
|
|
2358
|
-
progress.succeed('Analysis complete');
|
|
2359
3008
|
// Load entities WITH metadata from database
|
|
2360
3009
|
progress.start('Loading entity metadata...');
|
|
2361
3010
|
await initializeEnvironment();
|
|
@@ -2538,43 +3187,98 @@ async function handleDelete(scenarioId) {
|
|
|
2538
3187
|
* Creates isolation route directories and the layout guard file.
|
|
2539
3188
|
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2540
3189
|
*/
|
|
3190
|
+
function handleDesignSystem(designSystemId) {
|
|
3191
|
+
const root = process.cwd();
|
|
3192
|
+
const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
|
|
3193
|
+
if (!ds) {
|
|
3194
|
+
console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
|
|
3195
|
+
process.exit(1);
|
|
3196
|
+
}
|
|
3197
|
+
const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
|
|
3198
|
+
if (!fs.existsSync(srcPath)) {
|
|
3199
|
+
console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
|
|
3200
|
+
process.exit(1);
|
|
3201
|
+
}
|
|
3202
|
+
const destDir = path.join(root, '.codeyam');
|
|
3203
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
3204
|
+
const destPath = path.join(destDir, 'design-system.md');
|
|
3205
|
+
fs.copyFileSync(srcPath, destPath);
|
|
3206
|
+
console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
|
|
3207
|
+
}
|
|
2541
3208
|
function handleIsolate(componentNames) {
|
|
2542
3209
|
const root = process.cwd();
|
|
3210
|
+
const ctx = getTechStackContext(root);
|
|
2543
3211
|
if (componentNames.length === 0) {
|
|
2544
3212
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2545
3213
|
process.exit(1);
|
|
2546
3214
|
}
|
|
2547
3215
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2548
|
-
// Create
|
|
2549
|
-
|
|
3216
|
+
// Create the framework-appropriate layout guard if missing.
|
|
3217
|
+
// Clean up wrong-framework layout from a previous CLI version.
|
|
3218
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
3219
|
+
const wrongLayoutPath = path.join(isolateDir, ctx.isExpo ? 'layout.tsx' : '_layout.tsx');
|
|
3220
|
+
if (fs.existsSync(wrongLayoutPath)) {
|
|
3221
|
+
fs.unlinkSync(wrongLayoutPath);
|
|
3222
|
+
}
|
|
2550
3223
|
if (!fs.existsSync(layoutPath)) {
|
|
2551
3224
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
3225
|
+
if (ctx.isExpo) {
|
|
3226
|
+
fs.writeFileSync(layoutPath, [
|
|
3227
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
3228
|
+
'',
|
|
3229
|
+
'export default function CaptureLayout() {',
|
|
3230
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
3231
|
+
' return <Slot />;',
|
|
3232
|
+
'}',
|
|
3233
|
+
'',
|
|
3234
|
+
].join('\n'), 'utf8');
|
|
3235
|
+
}
|
|
3236
|
+
else {
|
|
3237
|
+
fs.writeFileSync(layoutPath, [
|
|
3238
|
+
'import { notFound } from "next/navigation";',
|
|
3239
|
+
'',
|
|
3240
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
3241
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
3242
|
+
' return <>{children}</>;',
|
|
3243
|
+
'}',
|
|
3244
|
+
'',
|
|
3245
|
+
].join('\n'), 'utf8');
|
|
3246
|
+
}
|
|
3247
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
3248
|
+
}
|
|
3249
|
+
// Create isolation route for each component.
|
|
3250
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2564
3251
|
const created = [];
|
|
2565
3252
|
const existed = [];
|
|
2566
3253
|
for (const name of componentNames) {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
3254
|
+
if (ctx.isExpo) {
|
|
3255
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
3256
|
+
if (fs.existsSync(filePath)) {
|
|
3257
|
+
existed.push(name);
|
|
3258
|
+
}
|
|
3259
|
+
else {
|
|
3260
|
+
created.push(name);
|
|
3261
|
+
}
|
|
2570
3262
|
}
|
|
2571
3263
|
else {
|
|
2572
|
-
|
|
2573
|
-
|
|
3264
|
+
const dir = path.join(isolateDir, name);
|
|
3265
|
+
if (fs.existsSync(dir)) {
|
|
3266
|
+
existed.push(name);
|
|
3267
|
+
}
|
|
3268
|
+
else {
|
|
3269
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3270
|
+
created.push(name);
|
|
3271
|
+
}
|
|
2574
3272
|
}
|
|
2575
3273
|
}
|
|
2576
3274
|
if (created.length > 0) {
|
|
2577
|
-
|
|
3275
|
+
if (ctx.isExpo) {
|
|
3276
|
+
console.log(chalk.green(`Ready for ${created.length} isolation route(s): ${created.map((n) => `app/isolated-components/${n}.tsx`).join(', ')}`));
|
|
3277
|
+
console.log(chalk.dim(' Create each file with useLocalSearchParams, a scenarios map, and nativeID="codeyam-capture"'));
|
|
3278
|
+
}
|
|
3279
|
+
else {
|
|
3280
|
+
console.log(chalk.green(`Created ${created.length} isolation route dir(s): ${created.join(', ')}`));
|
|
3281
|
+
}
|
|
2578
3282
|
}
|
|
2579
3283
|
if (existed.length > 0) {
|
|
2580
3284
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2628,6 +3332,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2628
3332
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2629
3333
|
if (data.sessionsNotified != null)
|
|
2630
3334
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
3335
|
+
// Surface the HTTP health check from the dev server
|
|
3336
|
+
if (data.preview) {
|
|
3337
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
3338
|
+
if (data.preview.error)
|
|
3339
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3340
|
+
}
|
|
2631
3341
|
return parts.join(' ');
|
|
2632
3342
|
}
|
|
2633
3343
|
case 'dev-server': {
|
|
@@ -2677,10 +3387,82 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2677
3387
|
}
|
|
2678
3388
|
return parts.join(' ');
|
|
2679
3389
|
}
|
|
3390
|
+
case 'verify-routes': {
|
|
3391
|
+
const lines = [];
|
|
3392
|
+
lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
|
|
3393
|
+
for (const [route, info] of Object.entries(data.routes || {})) {
|
|
3394
|
+
const r = info;
|
|
3395
|
+
if (r.ok) {
|
|
3396
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
|
|
3397
|
+
}
|
|
3398
|
+
else {
|
|
3399
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
for (const [route, info] of Object.entries(data.apiRoutes || {})) {
|
|
3403
|
+
const r = info;
|
|
3404
|
+
if (r.ok && r.isJSON) {
|
|
3405
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
|
|
3406
|
+
}
|
|
3407
|
+
else if (r.ok) {
|
|
3408
|
+
lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
|
|
3409
|
+
}
|
|
3410
|
+
else {
|
|
3411
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
lines.push('');
|
|
3415
|
+
lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
|
|
3416
|
+
return lines.join('\n');
|
|
3417
|
+
}
|
|
2680
3418
|
default:
|
|
2681
3419
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2682
3420
|
}
|
|
2683
3421
|
}
|
|
3422
|
+
/**
|
|
3423
|
+
* `codeyam editor handoff '{"summary":"..."}'`
|
|
3424
|
+
*
|
|
3425
|
+
* Update the handoff summary in .codeyam/config.json for other AI providers.
|
|
3426
|
+
*/
|
|
3427
|
+
function handleHandoff(jsonArg) {
|
|
3428
|
+
if (!jsonArg) {
|
|
3429
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3430
|
+
console.error(chalk.dim(' Usage: codeyam editor handoff \'{"summary":"Built the X component..."}\''));
|
|
3431
|
+
process.exit(1);
|
|
3432
|
+
}
|
|
3433
|
+
let summary;
|
|
3434
|
+
try {
|
|
3435
|
+
const parsed = JSON.parse(jsonArg);
|
|
3436
|
+
summary = parsed.summary;
|
|
3437
|
+
}
|
|
3438
|
+
catch {
|
|
3439
|
+
console.error(chalk.red('Error: Invalid JSON.'));
|
|
3440
|
+
process.exit(1);
|
|
3441
|
+
}
|
|
3442
|
+
if (!summary) {
|
|
3443
|
+
console.error(chalk.red('Error: "summary" field is required.'));
|
|
3444
|
+
process.exit(1);
|
|
3445
|
+
}
|
|
3446
|
+
const root = getProjectRoot();
|
|
3447
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3448
|
+
try {
|
|
3449
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3450
|
+
const state = readState(root);
|
|
3451
|
+
const provider = config.provider || 'claude';
|
|
3452
|
+
config.handoff = {
|
|
3453
|
+
summary,
|
|
3454
|
+
lastProvider: provider,
|
|
3455
|
+
lastStep: state?.step,
|
|
3456
|
+
lastUpdated: new Date().toISOString(),
|
|
3457
|
+
};
|
|
3458
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
3459
|
+
console.log(chalk.green('✓ Handoff summary updated in .codeyam/config.json'));
|
|
3460
|
+
}
|
|
3461
|
+
catch (err) {
|
|
3462
|
+
console.error(chalk.red(`Error: Could not update config.json: ${err.message}`));
|
|
3463
|
+
process.exit(1);
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
2684
3466
|
/**
|
|
2685
3467
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
2686
3468
|
*
|
|
@@ -2705,11 +3487,15 @@ async function handleRegister(jsonArg) {
|
|
|
2705
3487
|
}
|
|
2706
3488
|
// Normalize to array for uniform handling
|
|
2707
3489
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
3490
|
+
const root = getProjectRoot();
|
|
2708
3491
|
const port = getServerPort();
|
|
2709
3492
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2710
3493
|
const isBatch = items.length > 1;
|
|
2711
3494
|
let succeeded = 0;
|
|
2712
3495
|
let failed = 0;
|
|
3496
|
+
let warnings = 0;
|
|
3497
|
+
const issues = [];
|
|
3498
|
+
const screenshotEntries = [];
|
|
2713
3499
|
for (let i = 0; i < items.length; i++) {
|
|
2714
3500
|
const body = items[i];
|
|
2715
3501
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2737,6 +3523,12 @@ async function handleRegister(jsonArg) {
|
|
|
2737
3523
|
else {
|
|
2738
3524
|
parts.push(`errors=0`);
|
|
2739
3525
|
}
|
|
3526
|
+
if (data.visibleText) {
|
|
3527
|
+
const truncated = data.visibleText.length > 100
|
|
3528
|
+
? data.visibleText.substring(0, 97) + '...'
|
|
3529
|
+
: data.visibleText;
|
|
3530
|
+
parts.push(`visibleText="${truncated}"`);
|
|
3531
|
+
}
|
|
2740
3532
|
if (data.seedResult) {
|
|
2741
3533
|
if (data.seedResult.success) {
|
|
2742
3534
|
parts.push(`seed=ok`);
|
|
@@ -2772,12 +3564,29 @@ async function handleRegister(jsonArg) {
|
|
|
2772
3564
|
}
|
|
2773
3565
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2774
3566
|
}
|
|
3567
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2775
3568
|
if (!res.ok) {
|
|
2776
3569
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2777
3570
|
failed++;
|
|
2778
3571
|
}
|
|
2779
3572
|
else {
|
|
3573
|
+
if (resultIssues.length > 0)
|
|
3574
|
+
warnings++;
|
|
2780
3575
|
succeeded++;
|
|
3576
|
+
// Collect screenshot paths for duplicate detection
|
|
3577
|
+
if (data.screenshotCaptured &&
|
|
3578
|
+
data.scenario?.screenshotPath &&
|
|
3579
|
+
data.scenario?.name) {
|
|
3580
|
+
const absPath = path.join(root, '.codeyam', 'editor-scenarios', data.scenario.screenshotPath);
|
|
3581
|
+
screenshotEntries.push({
|
|
3582
|
+
scenarioName: data.scenario.name,
|
|
3583
|
+
screenshotAbsPath: absPath,
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
// Collect issues from this registration
|
|
3588
|
+
for (const issue of resultIssues) {
|
|
3589
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
2781
3590
|
}
|
|
2782
3591
|
}
|
|
2783
3592
|
catch (error) {
|
|
@@ -2788,10 +3597,50 @@ async function handleRegister(jsonArg) {
|
|
|
2788
3597
|
failed++;
|
|
2789
3598
|
}
|
|
2790
3599
|
}
|
|
3600
|
+
// Detect duplicate screenshots in the batch
|
|
3601
|
+
if (isBatch && screenshotEntries.length > 1) {
|
|
3602
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
3603
|
+
const hashEntries = screenshotEntries
|
|
3604
|
+
.map((e) => {
|
|
3605
|
+
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
3606
|
+
return hash
|
|
3607
|
+
? {
|
|
3608
|
+
scenarioName: e.scenarioName,
|
|
3609
|
+
screenshotPath: e.screenshotAbsPath,
|
|
3610
|
+
hash,
|
|
3611
|
+
}
|
|
3612
|
+
: null;
|
|
3613
|
+
})
|
|
3614
|
+
.filter((e) => e !== null);
|
|
3615
|
+
const duplicates = findDuplicateScreenshots(hashEntries);
|
|
3616
|
+
if (duplicates.size > 0) {
|
|
3617
|
+
console.log('');
|
|
3618
|
+
console.log(chalk.yellow.bold('WARNING: Identical screenshots detected:'));
|
|
3619
|
+
for (const [, names] of duplicates) {
|
|
3620
|
+
const quoted = names.map((n) => `"${n}"`);
|
|
3621
|
+
console.log(chalk.yellow(` ${quoted.join(' and ')} produced the same screenshot`));
|
|
3622
|
+
}
|
|
3623
|
+
console.log(chalk.yellow(" This usually means the app's view state depends on something scenarios can't control"));
|
|
3624
|
+
console.log(chalk.yellow(' (e.g., a hardcoded function return value, or identical localStorage not differentiating views).'));
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
2791
3627
|
if (isBatch) {
|
|
2792
|
-
console.log(
|
|
3628
|
+
console.log('');
|
|
3629
|
+
if (failed > 0 || warnings > 0) {
|
|
3630
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
3631
|
+
console.log('');
|
|
3632
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
3633
|
+
for (const issue of issues) {
|
|
3634
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
3635
|
+
}
|
|
3636
|
+
console.log('');
|
|
3637
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
3638
|
+
}
|
|
3639
|
+
else {
|
|
3640
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
3641
|
+
}
|
|
2793
3642
|
}
|
|
2794
|
-
if (failed > 0) {
|
|
3643
|
+
if (failed > 0 || warnings > 0) {
|
|
2795
3644
|
process.exit(1);
|
|
2796
3645
|
}
|
|
2797
3646
|
}
|
|
@@ -3003,6 +3852,8 @@ function handleChange(feature) {
|
|
|
3003
3852
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
3004
3853
|
printDimensionGuidance(dim, dimNames);
|
|
3005
3854
|
console.log(chalk.cyan(' The user is watching the preview. Let them see progress incrementally.'));
|
|
3855
|
+
console.log(chalk.red.bold(' NEVER claim "you should see X" or "the preview shows X" unless you just ran a preview command.'));
|
|
3856
|
+
console.log(chalk.red(' The preview only updates when you explicitly refresh it. Verify, then describe.'));
|
|
3006
3857
|
console.log();
|
|
3007
3858
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
3008
3859
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -3078,10 +3929,11 @@ function handleChange(feature) {
|
|
|
3078
3929
|
* Fetch the audit result from the server.
|
|
3079
3930
|
* Returns null if the server is unreachable.
|
|
3080
3931
|
*/
|
|
3081
|
-
async function fetchAuditResult() {
|
|
3932
|
+
async function fetchAuditResult(options) {
|
|
3082
3933
|
const port = getServerPort();
|
|
3934
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3083
3935
|
try {
|
|
3084
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3936
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3085
3937
|
if (!res.ok)
|
|
3086
3938
|
return null;
|
|
3087
3939
|
return await res.json();
|
|
@@ -3104,17 +3956,34 @@ async function checkAuditGate() {
|
|
|
3104
3956
|
return true; // Server unreachable — don't block
|
|
3105
3957
|
if (data.summary?.allPassing === true)
|
|
3106
3958
|
return true;
|
|
3107
|
-
//
|
|
3108
|
-
|
|
3109
|
-
|
|
3959
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3960
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3961
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3962
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3963
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3964
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3965
|
+
return true;
|
|
3966
|
+
}
|
|
3967
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
3110
3968
|
try {
|
|
3111
|
-
await
|
|
3969
|
+
const entities = await loadEntities({});
|
|
3970
|
+
if (entities && entities.length > 0) {
|
|
3971
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3972
|
+
const db = getDatabase();
|
|
3973
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3974
|
+
sha: e.sha,
|
|
3975
|
+
name: e.name,
|
|
3976
|
+
filePath: e.filePath || '',
|
|
3977
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3978
|
+
e.metadata?.namedExport === false,
|
|
3979
|
+
})));
|
|
3980
|
+
}
|
|
3112
3981
|
}
|
|
3113
3982
|
catch {
|
|
3114
|
-
|
|
3983
|
+
// Fall through
|
|
3115
3984
|
}
|
|
3116
|
-
// Re-check after
|
|
3117
|
-
const retry = await fetchAuditResult();
|
|
3985
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3986
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3118
3987
|
if (retry?.summary?.allPassing === true)
|
|
3119
3988
|
return true;
|
|
3120
3989
|
}
|
|
@@ -3137,6 +4006,8 @@ function printAuditGateFailures(data) {
|
|
|
3137
4006
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3138
4007
|
for (const c of missing) {
|
|
3139
4008
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
4009
|
+
if (c.hint)
|
|
4010
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3140
4011
|
}
|
|
3141
4012
|
}
|
|
3142
4013
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3150,7 +4021,15 @@ function printAuditGateFailures(data) {
|
|
|
3150
4021
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3151
4022
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3152
4023
|
for (const f of missing) {
|
|
3153
|
-
|
|
4024
|
+
if (f.testFile) {
|
|
4025
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
4026
|
+
}
|
|
4027
|
+
else {
|
|
4028
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
4029
|
+
if (f.suggestedTestFile) {
|
|
4030
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
3154
4033
|
}
|
|
3155
4034
|
}
|
|
3156
4035
|
if (s.functionsFailing > 0) {
|
|
@@ -3165,6 +4044,10 @@ function printAuditGateFailures(data) {
|
|
|
3165
4044
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3166
4045
|
for (const f of runnerErrors) {
|
|
3167
4046
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4047
|
+
if (f.hint)
|
|
4048
|
+
issues.push(` ${f.hint}`);
|
|
4049
|
+
else if (f.errorMessage)
|
|
4050
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
3168
4051
|
}
|
|
3169
4052
|
}
|
|
3170
4053
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3172,13 +4055,44 @@ function printAuditGateFailures(data) {
|
|
|
3172
4055
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3173
4056
|
for (const f of mismatch) {
|
|
3174
4057
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4058
|
+
if (f.hint)
|
|
4059
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3175
4060
|
}
|
|
3176
4061
|
}
|
|
3177
|
-
if (s.missingFromGlossary > 0)
|
|
4062
|
+
if (s.missingFromGlossary > 0) {
|
|
3178
4063
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
4064
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
4065
|
+
for (const m of missingGlossary) {
|
|
4066
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
4067
|
+
}
|
|
4068
|
+
const missingPaths = missingGlossary
|
|
4069
|
+
.map((m) => m.filePath)
|
|
4070
|
+
.filter(Boolean);
|
|
4071
|
+
if (missingPaths.length > 0) {
|
|
4072
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
|
|
4073
|
+
}
|
|
4074
|
+
else {
|
|
4075
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
3179
4078
|
if (s.incompleteEntities > 0) {
|
|
4079
|
+
// Check for persistent analysis failures
|
|
4080
|
+
let analysisFailures = {};
|
|
4081
|
+
try {
|
|
4082
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
4083
|
+
if (fs.existsSync(failuresPath)) {
|
|
4084
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
catch {
|
|
4088
|
+
// Non-fatal
|
|
4089
|
+
}
|
|
3180
4090
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3181
|
-
|
|
4091
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
4092
|
+
if (hasFailures) {
|
|
4093
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
4094
|
+
}
|
|
4095
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3182
4096
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3183
4097
|
}
|
|
3184
4098
|
else if (preCount > 0) {
|
|
@@ -3187,11 +4101,52 @@ function printAuditGateFailures(data) {
|
|
|
3187
4101
|
else {
|
|
3188
4102
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3189
4103
|
}
|
|
4104
|
+
const incomplete = data.incompleteEntities || [];
|
|
4105
|
+
for (const e of incomplete) {
|
|
4106
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4107
|
+
if (failureEntry) {
|
|
4108
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
4109
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
4110
|
+
}
|
|
4111
|
+
else {
|
|
4112
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
if (!hasFailures) {
|
|
4116
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
4117
|
+
}
|
|
3190
4118
|
}
|
|
3191
|
-
if (s.
|
|
4119
|
+
if (s.unassociatedScenarios > 0) {
|
|
4120
|
+
const unassociated = data.unassociatedScenarios || [];
|
|
4121
|
+
const unassocPaths = unassociated
|
|
4122
|
+
.map((u) => u.filePath)
|
|
4123
|
+
.filter(Boolean);
|
|
4124
|
+
if (unassocPaths.length > 0) {
|
|
4125
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
|
|
4126
|
+
}
|
|
4127
|
+
else {
|
|
4128
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
4129
|
+
}
|
|
4130
|
+
for (const u of unassociated) {
|
|
4131
|
+
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3192
4135
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3193
|
-
|
|
4136
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
4137
|
+
for (const m of miscategorized) {
|
|
4138
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
4139
|
+
}
|
|
4140
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
4141
|
+
}
|
|
4142
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
3194
4143
|
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
4144
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
4145
|
+
for (const r of recapture) {
|
|
4146
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
4147
|
+
}
|
|
4148
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
4149
|
+
}
|
|
3195
4150
|
if (issues.length > 0) {
|
|
3196
4151
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3197
4152
|
for (const issue of issues) {
|
|
@@ -3226,40 +4181,99 @@ function printAuditGateFailures(data) {
|
|
|
3226
4181
|
* which glossary components have registered scenarios and which functions
|
|
3227
4182
|
* have test files. Exits with code 1 if anything is missing.
|
|
3228
4183
|
*/
|
|
3229
|
-
async function handleAudit() {
|
|
4184
|
+
async function handleAudit(options) {
|
|
3230
4185
|
let data = await fetchAuditResult();
|
|
3231
4186
|
if (!data) {
|
|
3232
4187
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3233
4188
|
process.exit(1);
|
|
3234
4189
|
}
|
|
3235
|
-
//
|
|
3236
|
-
//
|
|
3237
|
-
//
|
|
3238
|
-
//
|
|
4190
|
+
// Two-phase auto-fix for entity associations:
|
|
4191
|
+
// Phase 1: DB-only backfill — fast, matches existing entities to scenarios.
|
|
4192
|
+
// Phase 2: If backfill fails, targeted analyze-imports for only the
|
|
4193
|
+
// specific unresolved files (1-3 files, not the full glossary scan).
|
|
3239
4194
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
4195
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3240
4196
|
let autoRemediationFailed = false;
|
|
3241
|
-
if (incompleteBeforeFix.length > 0) {
|
|
3242
|
-
|
|
4197
|
+
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
4198
|
+
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
4199
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
4200
|
+
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3243
4201
|
try {
|
|
3244
|
-
await
|
|
4202
|
+
const entities = await loadEntities({});
|
|
4203
|
+
if (entities && entities.length > 0) {
|
|
4204
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4205
|
+
const db = getDatabase();
|
|
4206
|
+
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
4207
|
+
sha: e.sha,
|
|
4208
|
+
name: e.name,
|
|
4209
|
+
filePath: e.filePath || '',
|
|
4210
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4211
|
+
e.metadata?.namedExport === false,
|
|
4212
|
+
})));
|
|
4213
|
+
}
|
|
3245
4214
|
}
|
|
3246
4215
|
catch {
|
|
3247
|
-
// Fall through —
|
|
4216
|
+
// Fall through — re-fetch will show remaining issues
|
|
3248
4217
|
}
|
|
3249
|
-
// Re-fetch audit results after the
|
|
3250
|
-
|
|
4218
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
4219
|
+
// since they haven't changed (backfill is DB-only).
|
|
4220
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3251
4221
|
if (!data) {
|
|
3252
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
4222
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3253
4223
|
process.exit(1);
|
|
3254
4224
|
}
|
|
3255
|
-
// If
|
|
3256
|
-
//
|
|
4225
|
+
// If issues persist after DB-only backfill, run targeted analyze-imports
|
|
4226
|
+
// for ONLY the specific files that need entities. This is fast (1-3 files)
|
|
4227
|
+
// unlike full analyze-imports which scans all glossary entries.
|
|
4228
|
+
const incompleteAfterBackfill = data.incompleteEntities || [];
|
|
4229
|
+
const unassociatedAfterBackfill = data.unassociatedScenarios || [];
|
|
4230
|
+
if (incompleteAfterBackfill.length > 0 ||
|
|
4231
|
+
unassociatedAfterBackfill.length > 0) {
|
|
4232
|
+
const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
|
|
4233
|
+
const targetPaths = determineTargetedAnalysisPaths({
|
|
4234
|
+
unassociatedScenarios: unassociatedAfterBackfill,
|
|
4235
|
+
incompleteEntities: incompleteAfterBackfill,
|
|
4236
|
+
});
|
|
4237
|
+
if (targetPaths.length > 0) {
|
|
4238
|
+
console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
|
|
4239
|
+
try {
|
|
4240
|
+
await handleAnalyzeImports({
|
|
4241
|
+
silent: true,
|
|
4242
|
+
filePaths: targetPaths,
|
|
4243
|
+
});
|
|
4244
|
+
// Retry backfill after analysis created entities
|
|
4245
|
+
const entities = await loadEntities({});
|
|
4246
|
+
if (entities && entities.length > 0) {
|
|
4247
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4248
|
+
const freshDb = getDb();
|
|
4249
|
+
await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
|
|
4250
|
+
sha: e.sha,
|
|
4251
|
+
name: e.name,
|
|
4252
|
+
filePath: e.filePath || '',
|
|
4253
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4254
|
+
e.metadata?.namedExport === false,
|
|
4255
|
+
})));
|
|
4256
|
+
}
|
|
4257
|
+
// Re-fetch audit results after targeted analysis
|
|
4258
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
4259
|
+
if (!data) {
|
|
4260
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
|
|
4261
|
+
process.exit(1);
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
catch {
|
|
4265
|
+
// Targeted analysis failed — fall through to show remaining issues
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
// Check if issues persist after all remediation attempts
|
|
3257
4270
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3258
|
-
|
|
4271
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
4272
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3259
4273
|
autoRemediationFailed = true;
|
|
3260
4274
|
}
|
|
3261
4275
|
}
|
|
3262
|
-
|
|
4276
|
+
let { components, functions, summary } = data;
|
|
3263
4277
|
console.log();
|
|
3264
4278
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
3265
4279
|
console.log();
|
|
@@ -3289,6 +4303,9 @@ async function handleAudit() {
|
|
|
3289
4303
|
}
|
|
3290
4304
|
else {
|
|
3291
4305
|
detail = chalk.red(' — no scenarios registered');
|
|
4306
|
+
if (c.hint) {
|
|
4307
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
4308
|
+
}
|
|
3292
4309
|
}
|
|
3293
4310
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3294
4311
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3314,14 +4331,20 @@ async function handleAudit() {
|
|
|
3314
4331
|
let detail;
|
|
3315
4332
|
switch (f.status) {
|
|
3316
4333
|
case 'ok':
|
|
3317
|
-
|
|
4334
|
+
if (f.testCaseCount !== undefined && f.testCaseCount < 3) {
|
|
4335
|
+
detail = chalk.yellow(` (${f.testFile}) — ⚠ only ${f.testCaseCount} test case${f.testCaseCount !== 1 ? 's' : ''}, consider adding more`);
|
|
4336
|
+
}
|
|
4337
|
+
else {
|
|
4338
|
+
detail = chalk.dim(` (${f.testFile}${f.testCaseCount !== undefined ? `, ${f.testCaseCount} tests` : ''})`);
|
|
4339
|
+
}
|
|
3318
4340
|
break;
|
|
3319
4341
|
case 'runner_error':
|
|
3320
4342
|
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
3321
4343
|
if (f.errorMessage) {
|
|
3322
4344
|
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
3323
4345
|
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
3324
|
-
|
|
4346
|
+
const ctx = getTechStackContext(process.cwd());
|
|
4347
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
3325
4348
|
}
|
|
3326
4349
|
break;
|
|
3327
4350
|
case 'failing':
|
|
@@ -3332,13 +4355,23 @@ async function handleAudit() {
|
|
|
3332
4355
|
break;
|
|
3333
4356
|
case 'missing':
|
|
3334
4357
|
default:
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
4358
|
+
if (f.testFile) {
|
|
4359
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
4360
|
+
}
|
|
4361
|
+
else {
|
|
4362
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
4363
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
4364
|
+
if (f.suggestedTestFile) {
|
|
4365
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
3338
4368
|
break;
|
|
3339
4369
|
}
|
|
3340
4370
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
3341
4371
|
}
|
|
4372
|
+
if (summary.functionsThinCoverage > 0) {
|
|
4373
|
+
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.`));
|
|
4374
|
+
}
|
|
3342
4375
|
console.log();
|
|
3343
4376
|
}
|
|
3344
4377
|
// Missing from glossary
|
|
@@ -3348,26 +4381,85 @@ async function handleAudit() {
|
|
|
3348
4381
|
for (const m of missingFromGlossary) {
|
|
3349
4382
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3350
4383
|
}
|
|
3351
|
-
|
|
4384
|
+
const mgPaths = missingFromGlossary
|
|
4385
|
+
.map((m) => m.filePath)
|
|
4386
|
+
.filter(Boolean);
|
|
4387
|
+
if (mgPaths.length > 0) {
|
|
4388
|
+
console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
|
|
4389
|
+
}
|
|
4390
|
+
else {
|
|
4391
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
4392
|
+
}
|
|
3352
4393
|
console.log();
|
|
3353
4394
|
}
|
|
3354
|
-
// Incomplete entities (scenarios without analyses)
|
|
4395
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
4396
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
4397
|
+
// template which is slow even for a few files. Users should run
|
|
4398
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3355
4399
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3356
4400
|
if (incompleteEntities.length > 0) {
|
|
4401
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
4402
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
4403
|
+
// Check for persistent analysis failures
|
|
4404
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3357
4405
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3358
4406
|
for (const e of incompleteEntities) {
|
|
3359
|
-
|
|
4407
|
+
// Check if this entity has a persistent failure
|
|
4408
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4409
|
+
if (failureEntry) {
|
|
4410
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
4411
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
4412
|
+
const guidance = formatManualAnalysisGuidance({
|
|
4413
|
+
name: e.name,
|
|
4414
|
+
filePath: filePath || '',
|
|
4415
|
+
scenarioCount: e.scenarioCount,
|
|
4416
|
+
error: failureEntry.error,
|
|
4417
|
+
});
|
|
4418
|
+
// Print each line with proper indentation
|
|
4419
|
+
for (const line of guidance.split('\n')) {
|
|
4420
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
else {
|
|
4424
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
4425
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
4426
|
+
}
|
|
3360
4427
|
}
|
|
4428
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4429
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
4430
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
4431
|
+
}
|
|
4432
|
+
console.log();
|
|
4433
|
+
}
|
|
4434
|
+
// Unassociated scenarios (NULL entity_sha with file paths)
|
|
4435
|
+
const unassociatedScenarios = data.unassociatedScenarios || [];
|
|
4436
|
+
if (unassociatedScenarios.length > 0) {
|
|
4437
|
+
console.log(chalk.bold('Unassociated scenarios (missing entity link):'));
|
|
4438
|
+
for (const u of unassociatedScenarios) {
|
|
4439
|
+
console.log(` ${chalk.red('✗')} ${u.name} ${chalk.dim(`(${u.filePath})`)} — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''} with no entity_sha`);
|
|
4440
|
+
for (const sn of u.scenarioNames.slice(0, 3)) {
|
|
4441
|
+
console.log(chalk.dim(` "${sn}"`));
|
|
4442
|
+
}
|
|
4443
|
+
if (u.scenarioNames.length > 3) {
|
|
4444
|
+
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
const uaPaths = unassociatedScenarios
|
|
4448
|
+
.map((u) => u.filePath)
|
|
4449
|
+
.filter(Boolean);
|
|
4450
|
+
const uaCmd = uaPaths.length > 0
|
|
4451
|
+
? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
|
|
4452
|
+
: 'codeyam editor analyze-imports';
|
|
3361
4453
|
if (autoRemediationFailed) {
|
|
3362
|
-
console.log(chalk.
|
|
3363
|
-
console.log(chalk.
|
|
3364
|
-
console.log(chalk.yellow(' This means the analysis failed for these entities. Common causes:'));
|
|
3365
|
-
console.log(chalk.yellow(' • Source file was renamed/deleted but glossary still references the old path'));
|
|
3366
|
-
console.log(chalk.yellow(' • Entity has import errors that prevent analysis'));
|
|
3367
|
-
console.log(chalk.yellow(' To investigate: run `codeyam editor analyze-imports` (without --silent) to see the full error.'));
|
|
4454
|
+
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
4455
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
|
|
3368
4456
|
}
|
|
3369
4457
|
else {
|
|
3370
|
-
console.log(chalk.yellow(
|
|
4458
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
|
|
4459
|
+
}
|
|
4460
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4461
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
4462
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3371
4463
|
}
|
|
3372
4464
|
console.log();
|
|
3373
4465
|
}
|
|
@@ -3396,7 +4488,26 @@ async function handleAudit() {
|
|
|
3396
4488
|
: `${s.status.status}`;
|
|
3397
4489
|
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3398
4490
|
}
|
|
3399
|
-
|
|
4491
|
+
if (options?.fix) {
|
|
4492
|
+
// --fix: auto-recapture stale scenarios instead of just reporting them
|
|
4493
|
+
const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
|
|
4494
|
+
if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
|
|
4495
|
+
console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
|
|
4496
|
+
console.log();
|
|
4497
|
+
await handleRecaptureStale();
|
|
4498
|
+
// Re-fetch audit results so the summary and exit code reflect
|
|
4499
|
+
// the post-fix state, not the pre-fix state.
|
|
4500
|
+
const refreshed = await fetchAuditResult({ skipTests: true });
|
|
4501
|
+
if (refreshed) {
|
|
4502
|
+
data = refreshed;
|
|
4503
|
+
({ components, functions, summary } = data);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
else {
|
|
4508
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
4509
|
+
console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
|
|
4510
|
+
}
|
|
3400
4511
|
console.log();
|
|
3401
4512
|
}
|
|
3402
4513
|
// Duplicate glossary names (warning, not a failure)
|
|
@@ -3444,6 +4555,9 @@ async function handleAudit() {
|
|
|
3444
4555
|
if (summary.incompleteEntities > 0) {
|
|
3445
4556
|
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
3446
4557
|
}
|
|
4558
|
+
if (summary.unassociatedScenarios > 0) {
|
|
4559
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
4560
|
+
}
|
|
3447
4561
|
if (summary.miscategorizedScenarios > 0) {
|
|
3448
4562
|
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
3449
4563
|
}
|
|
@@ -3456,17 +4570,6 @@ async function handleAudit() {
|
|
|
3456
4570
|
if (!allOk) {
|
|
3457
4571
|
process.exit(1);
|
|
3458
4572
|
}
|
|
3459
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3460
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3461
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3462
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3463
|
-
try {
|
|
3464
|
-
await handleAnalyzeImports({ silent: true });
|
|
3465
|
-
}
|
|
3466
|
-
catch {
|
|
3467
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3468
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3469
|
-
}
|
|
3470
4573
|
}
|
|
3471
4574
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3472
4575
|
/**
|
|
@@ -3673,14 +4776,14 @@ async function handleScenarioCoverage() {
|
|
|
3673
4776
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3674
4777
|
try {
|
|
3675
4778
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3676
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4779
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3677
4780
|
const db = getDatabase();
|
|
3678
4781
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3679
4782
|
if (backfillCount > 0) {
|
|
3680
4783
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3681
4784
|
await handleAnalyzeImports({ silent: true });
|
|
3682
4785
|
// Run backfill after analysis
|
|
3683
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4786
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3684
4787
|
const entities = await loadEntities({});
|
|
3685
4788
|
if (entities && entities.length > 0) {
|
|
3686
4789
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -3827,7 +4930,7 @@ async function handleTemplate() {
|
|
|
3827
4930
|
_: [],
|
|
3828
4931
|
});
|
|
3829
4932
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
3830
|
-
// 5. Verify config has startCommand
|
|
4933
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
3831
4934
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3832
4935
|
if (fs.existsSync(configPath)) {
|
|
3833
4936
|
try {
|
|
@@ -3838,6 +4941,30 @@ async function handleTemplate() {
|
|
|
3838
4941
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
3839
4942
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
3840
4943
|
}
|
|
4944
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4945
|
+
if (stack?.supportedFormats) {
|
|
4946
|
+
config.appFormats = stack.supportedFormats;
|
|
4947
|
+
}
|
|
4948
|
+
// Pre-populate tech stack from template
|
|
4949
|
+
if (stack) {
|
|
4950
|
+
config.techStack = getTechStackForTemplate(stack.id);
|
|
4951
|
+
}
|
|
4952
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4953
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4954
|
+
config.defaultScreenSize = {
|
|
4955
|
+
name: 'iPhone 16',
|
|
4956
|
+
width: 393,
|
|
4957
|
+
height: 852,
|
|
4958
|
+
};
|
|
4959
|
+
config.screenSizes = {
|
|
4960
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4961
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4962
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4963
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4964
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4965
|
+
};
|
|
4966
|
+
}
|
|
4967
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3841
4968
|
}
|
|
3842
4969
|
catch {
|
|
3843
4970
|
// Config parse error is non-fatal
|
|
@@ -3872,7 +4999,15 @@ async function handleTemplate() {
|
|
|
3872
4999
|
}
|
|
3873
5000
|
console.log();
|
|
3874
5001
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
3875
|
-
|
|
5002
|
+
if (stack?.id === 'expo-react-native') {
|
|
5003
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
5004
|
+
}
|
|
5005
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
5006
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
5007
|
+
}
|
|
5008
|
+
else {
|
|
5009
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
5010
|
+
}
|
|
3876
5011
|
}
|
|
3877
5012
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
3878
5013
|
/**
|
|
@@ -4177,14 +5312,205 @@ function handleEditorDebug(args) {
|
|
|
4177
5312
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4178
5313
|
console.log();
|
|
4179
5314
|
}
|
|
5315
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
5316
|
+
/**
|
|
5317
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
5318
|
+
*
|
|
5319
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
5320
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
5321
|
+
* needing the analyzer template to parse the source file.
|
|
5322
|
+
*/
|
|
5323
|
+
async function handleManualEntity(jsonArg) {
|
|
5324
|
+
const root = getProjectRoot();
|
|
5325
|
+
// Parse JSON input (supports @file.json convention)
|
|
5326
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
5327
|
+
if (parsed.error || !parsed.body) {
|
|
5328
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
5329
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
5330
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
5331
|
+
process.exit(1);
|
|
5332
|
+
}
|
|
5333
|
+
const input = parsed.body;
|
|
5334
|
+
// Validate input
|
|
5335
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
5336
|
+
// Read glossary for validation
|
|
5337
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
5338
|
+
let glossaryEntries = [];
|
|
5339
|
+
try {
|
|
5340
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
5341
|
+
}
|
|
5342
|
+
catch {
|
|
5343
|
+
// Empty glossary — validation will still check file existence
|
|
5344
|
+
}
|
|
5345
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
5346
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
5347
|
+
});
|
|
5348
|
+
if (errors.length > 0) {
|
|
5349
|
+
console.error(chalk.red('Validation errors:'));
|
|
5350
|
+
for (const err of errors) {
|
|
5351
|
+
console.error(chalk.red(` • ${err}`));
|
|
5352
|
+
}
|
|
5353
|
+
process.exit(1);
|
|
5354
|
+
}
|
|
5355
|
+
// Read source file and compute SHA
|
|
5356
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
5357
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
5358
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
5359
|
+
// Get project and branch
|
|
5360
|
+
await initializeEnvironment();
|
|
5361
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
5362
|
+
let projectSlug;
|
|
5363
|
+
try {
|
|
5364
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
5365
|
+
projectSlug = config.projectSlug;
|
|
5366
|
+
}
|
|
5367
|
+
catch {
|
|
5368
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
5369
|
+
process.exit(1);
|
|
5370
|
+
}
|
|
5371
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
5372
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
5373
|
+
const db = getDatabase();
|
|
5374
|
+
// Convert type info to dataForMocks format
|
|
5375
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
5376
|
+
// Create entity record
|
|
5377
|
+
const entityMetadata = {
|
|
5378
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
5379
|
+
name: ie.name,
|
|
5380
|
+
filePath: ie.filePath,
|
|
5381
|
+
})),
|
|
5382
|
+
manuallyAnalyzed: true,
|
|
5383
|
+
};
|
|
5384
|
+
// Check if entity already exists
|
|
5385
|
+
const existingEntity = await db
|
|
5386
|
+
.selectFrom('entities')
|
|
5387
|
+
.select('sha')
|
|
5388
|
+
.where('sha', '=', entitySha)
|
|
5389
|
+
.executeTakeFirst();
|
|
5390
|
+
if (!existingEntity) {
|
|
5391
|
+
await db
|
|
5392
|
+
.insertInto('entities')
|
|
5393
|
+
.values({
|
|
5394
|
+
sha: entitySha,
|
|
5395
|
+
project_id: project.id,
|
|
5396
|
+
name: input.name,
|
|
5397
|
+
entity_type: input.entityType,
|
|
5398
|
+
file_path: input.filePath,
|
|
5399
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5400
|
+
})
|
|
5401
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
5402
|
+
.execute();
|
|
5403
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5404
|
+
}
|
|
5405
|
+
else {
|
|
5406
|
+
// Update metadata on existing entity
|
|
5407
|
+
await db
|
|
5408
|
+
.updateTable('entities')
|
|
5409
|
+
.set({
|
|
5410
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5411
|
+
})
|
|
5412
|
+
.where('sha', '=', entitySha)
|
|
5413
|
+
.execute();
|
|
5414
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5415
|
+
}
|
|
5416
|
+
// Create entity_branch record
|
|
5417
|
+
await db
|
|
5418
|
+
.insertInto('entity_branches')
|
|
5419
|
+
.values({
|
|
5420
|
+
entity_sha: entitySha,
|
|
5421
|
+
branch_id: branch.id,
|
|
5422
|
+
active: true,
|
|
5423
|
+
})
|
|
5424
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
5425
|
+
.execute();
|
|
5426
|
+
// Create analysis record
|
|
5427
|
+
const { randomUUID } = await import('crypto');
|
|
5428
|
+
const analysisId = randomUUID();
|
|
5429
|
+
const now = new Date().toISOString();
|
|
5430
|
+
const analysisMetadata = {
|
|
5431
|
+
scenariosDataStructure: {
|
|
5432
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
5433
|
+
},
|
|
5434
|
+
mergedDataStructure: {
|
|
5435
|
+
dependencySchemas: null,
|
|
5436
|
+
},
|
|
5437
|
+
manuallyAnalyzed: true,
|
|
5438
|
+
};
|
|
5439
|
+
const analysisStatus = {
|
|
5440
|
+
startedAt: now,
|
|
5441
|
+
finishedAt: now,
|
|
5442
|
+
steps: [],
|
|
5443
|
+
errors: [],
|
|
5444
|
+
};
|
|
5445
|
+
// Check if analysis already exists for this entity
|
|
5446
|
+
const existingAnalysis = await db
|
|
5447
|
+
.selectFrom('analyses')
|
|
5448
|
+
.select('id')
|
|
5449
|
+
.where('entity_sha', '=', entitySha)
|
|
5450
|
+
.executeTakeFirst();
|
|
5451
|
+
if (!existingAnalysis) {
|
|
5452
|
+
await db
|
|
5453
|
+
.insertInto('analyses')
|
|
5454
|
+
.values({
|
|
5455
|
+
id: analysisId,
|
|
5456
|
+
project_id: project.id,
|
|
5457
|
+
entity_sha: entitySha,
|
|
5458
|
+
entity_name: input.name,
|
|
5459
|
+
entity_type: input.entityType,
|
|
5460
|
+
file_path: input.filePath,
|
|
5461
|
+
status: JSON.stringify(analysisStatus),
|
|
5462
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5463
|
+
})
|
|
5464
|
+
.execute();
|
|
5465
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
5466
|
+
}
|
|
5467
|
+
else {
|
|
5468
|
+
// Update existing analysis with manual metadata
|
|
5469
|
+
await db
|
|
5470
|
+
.updateTable('analyses')
|
|
5471
|
+
.set({
|
|
5472
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5473
|
+
status: JSON.stringify(analysisStatus),
|
|
5474
|
+
})
|
|
5475
|
+
.where('entity_sha', '=', entitySha)
|
|
5476
|
+
.execute();
|
|
5477
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
5478
|
+
}
|
|
5479
|
+
// Backfill entity_sha on scenarios
|
|
5480
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
5481
|
+
{
|
|
5482
|
+
sha: entitySha,
|
|
5483
|
+
name: input.name,
|
|
5484
|
+
filePath: input.filePath,
|
|
5485
|
+
isDefaultExport: false,
|
|
5486
|
+
},
|
|
5487
|
+
]);
|
|
5488
|
+
if (backfillResult.updated > 0) {
|
|
5489
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
5490
|
+
}
|
|
5491
|
+
// Clear from analysis failures tracker
|
|
5492
|
+
const failures = readAnalysisFailures(root);
|
|
5493
|
+
if (failures[input.filePath]) {
|
|
5494
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
5495
|
+
writeAnalysisFailures(root, updated);
|
|
5496
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
5497
|
+
}
|
|
5498
|
+
console.log();
|
|
5499
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
5500
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
5501
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
5502
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
5503
|
+
console.log();
|
|
5504
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
5505
|
+
}
|
|
4180
5506
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4181
5507
|
const editorCommand = {
|
|
4182
5508
|
command: 'editor [step] [json]',
|
|
4183
5509
|
describe: 'Editor mode guided workflow',
|
|
4184
5510
|
builder: (yargs) => {
|
|
4185
5511
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4186
|
-
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)'
|
|
4187
|
-
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors)';
|
|
5512
|
+
? '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)'
|
|
5513
|
+
: '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)';
|
|
4188
5514
|
let builder = yargs
|
|
4189
5515
|
.positional('step', {
|
|
4190
5516
|
type: 'string',
|
|
@@ -4215,6 +5541,11 @@ const editorCommand = {
|
|
|
4215
5541
|
alias: 'p',
|
|
4216
5542
|
describe: 'Port to run the web server on',
|
|
4217
5543
|
default: 3111,
|
|
5544
|
+
})
|
|
5545
|
+
.option('fix', {
|
|
5546
|
+
type: 'boolean',
|
|
5547
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
5548
|
+
default: false,
|
|
4218
5549
|
});
|
|
4219
5550
|
if (IS_INTERNAL_BUILD) {
|
|
4220
5551
|
builder = builder
|
|
@@ -4245,6 +5576,18 @@ const editorCommand = {
|
|
|
4245
5576
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
4246
5577
|
// journal, journal-update, dev-server, client-errors
|
|
4247
5578
|
if (argv.step && EDITOR_API_SUBCOMMANDS.includes(argv.step)) {
|
|
5579
|
+
// Guard: commit requires step 16 to have been run first.
|
|
5580
|
+
// Without this, Claude shortcuts the process by running
|
|
5581
|
+
// `codeyam editor commit` directly from step 15, skipping
|
|
5582
|
+
// steps 16-18 entirely.
|
|
5583
|
+
if (argv.step === 'commit') {
|
|
5584
|
+
const state = readState(root);
|
|
5585
|
+
if (state && state.step !== 16) {
|
|
5586
|
+
console.error(chalk.red('Error: Run `codeyam editor 16` before committing.'));
|
|
5587
|
+
console.error(chalk.dim(` Current step: ${state.step} (${state.label || 'unknown'}). Step 16 (Commit) must be run first.`));
|
|
5588
|
+
process.exit(1);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
4248
5591
|
const port = getServerPort();
|
|
4249
5592
|
try {
|
|
4250
5593
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -4278,6 +5621,11 @@ const editorCommand = {
|
|
|
4278
5621
|
}
|
|
4279
5622
|
return;
|
|
4280
5623
|
}
|
|
5624
|
+
// Subcommand: codeyam editor handoff '{"summary":"..."}'
|
|
5625
|
+
if (argv.step === 'handoff') {
|
|
5626
|
+
await handleHandoff(argv.json || '');
|
|
5627
|
+
return;
|
|
5628
|
+
}
|
|
4281
5629
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
4282
5630
|
if (argv.step === 'register') {
|
|
4283
5631
|
await handleRegister(argv.json || '');
|
|
@@ -4288,9 +5636,58 @@ const editorCommand = {
|
|
|
4288
5636
|
await handleGlossaryAdd(argv.json || '');
|
|
4289
5637
|
return;
|
|
4290
5638
|
}
|
|
4291
|
-
// Subcommand: codeyam editor
|
|
5639
|
+
// Subcommand: codeyam editor task-ontrack
|
|
5640
|
+
// Corrective command when Claude advanced without creating a task.
|
|
5641
|
+
if (argv.step === 'task-ontrack') {
|
|
5642
|
+
const state = readState(root);
|
|
5643
|
+
if (!state?.step) {
|
|
5644
|
+
console.error(chalk.red('No editor state found. Run `codeyam editor 1` to start.'));
|
|
5645
|
+
process.exit(1);
|
|
5646
|
+
}
|
|
5647
|
+
const currentLabel = STEP_LABELS[state.step] || `Step ${state.step}`;
|
|
5648
|
+
const totalSteps = Object.keys(STEP_LABELS).length;
|
|
5649
|
+
const nextLabel = state.step < totalSteps ? STEP_LABELS[state.step + 1] : undefined;
|
|
5650
|
+
console.log();
|
|
5651
|
+
console.log(chalk.bold.yellow('━━━ GETTING BACK ON TRACK ━━━'));
|
|
5652
|
+
console.log();
|
|
5653
|
+
console.log(chalk.yellow('You went off-track by not creating a task. Create the task below to continue.'));
|
|
5654
|
+
console.log();
|
|
5655
|
+
// Print the TASK directive for the current step
|
|
5656
|
+
let taskTitle;
|
|
5657
|
+
if (state.step < totalSteps) {
|
|
5658
|
+
taskTitle = `Complete codeyam editor step ${state.step}: '${currentLabel}' and move on to step ${state.step + 1}: '${nextLabel}'`;
|
|
5659
|
+
}
|
|
5660
|
+
else {
|
|
5661
|
+
taskTitle =
|
|
5662
|
+
'Ask user what to build next and restart codeyam editor workflow';
|
|
5663
|
+
}
|
|
5664
|
+
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
5665
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
5666
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
5667
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
5668
|
+
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
5669
|
+
console.log();
|
|
5670
|
+
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
5671
|
+
console.log();
|
|
5672
|
+
console.log(chalk.green(`After creating the task, re-run: codeyam editor ${state.step + 1}`));
|
|
5673
|
+
console.log();
|
|
5674
|
+
// Mark task as expected-created so the next step can proceed.
|
|
5675
|
+
// The hook will set taskCreated=true when it sees the actual TaskCreate call.
|
|
5676
|
+
// But we also allow advancement now since task-ontrack itself is the corrective action.
|
|
5677
|
+
const trackingPath = path.join(root, '.codeyam', 'editor-task-tracking.json');
|
|
5678
|
+
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
5679
|
+
return;
|
|
5680
|
+
}
|
|
5681
|
+
// Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
|
|
4292
5682
|
if (argv.step === 'analyze-imports') {
|
|
4293
|
-
await
|
|
5683
|
+
const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
|
|
5684
|
+
const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
|
|
5685
|
+
await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
|
|
5686
|
+
return;
|
|
5687
|
+
}
|
|
5688
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
5689
|
+
if (argv.step === 'manual-entity') {
|
|
5690
|
+
await handleManualEntity(argv.json || '');
|
|
4294
5691
|
return;
|
|
4295
5692
|
}
|
|
4296
5693
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -4298,9 +5695,9 @@ const editorCommand = {
|
|
|
4298
5695
|
await handleDependents(argv.json || '');
|
|
4299
5696
|
return;
|
|
4300
5697
|
}
|
|
4301
|
-
// Subcommand: codeyam editor audit
|
|
5698
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
4302
5699
|
if (argv.step === 'audit') {
|
|
4303
|
-
await handleAudit();
|
|
5700
|
+
await handleAudit({ fix: argv.fix || false });
|
|
4304
5701
|
return;
|
|
4305
5702
|
}
|
|
4306
5703
|
// Subcommand: codeyam editor scenarios
|
|
@@ -4333,6 +5730,11 @@ const editorCommand = {
|
|
|
4333
5730
|
await handleValidateSeed(argv.json || '');
|
|
4334
5731
|
return;
|
|
4335
5732
|
}
|
|
5733
|
+
// Subcommand: codeyam editor design-system <id>
|
|
5734
|
+
if (argv.step === 'design-system') {
|
|
5735
|
+
handleDesignSystem(argv.json || '');
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
4336
5738
|
// Subcommand: codeyam editor delete <scenarioId>
|
|
4337
5739
|
if (argv.step === 'delete') {
|
|
4338
5740
|
await handleDelete(argv.json || '');
|
|
@@ -4406,10 +5808,18 @@ const editorCommand = {
|
|
|
4406
5808
|
try {
|
|
4407
5809
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4408
5810
|
const { projectSlug } = config;
|
|
5811
|
+
// Detect provider switch and inform the user
|
|
5812
|
+
const handoff = config.handoff;
|
|
5813
|
+
const currentProvider = config.provider || 'claude';
|
|
5814
|
+
if (handoff?.lastProvider && handoff.lastProvider !== currentProvider) {
|
|
5815
|
+
console.log(chalk.yellow(` Provider changed: ${handoff.lastProvider} → ${currentProvider}`));
|
|
5816
|
+
console.log(chalk.dim(' The editor will offer to continue with handoff context.'));
|
|
5817
|
+
}
|
|
4409
5818
|
if (!projectSlug) {
|
|
4410
5819
|
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
4411
5820
|
return;
|
|
4412
5821
|
}
|
|
5822
|
+
console.log(chalk.dim(` CodeYam Editor v${getDisplayVersion()}`));
|
|
4413
5823
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
4414
5824
|
if (!connectionOk) {
|
|
4415
5825
|
errorLog('Environment validation failed');
|
|
@@ -4533,13 +5943,24 @@ const editorCommand = {
|
|
|
4533
5943
|
try {
|
|
4534
5944
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4535
5945
|
const seedDb = getDb();
|
|
4536
|
-
|
|
5946
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5947
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5948
|
+
const homeScenario = await seedDb
|
|
4537
5949
|
.selectFrom('editor_scenarios')
|
|
4538
5950
|
.select(['id', 'name', 'type'])
|
|
4539
5951
|
.where('project_id', '=', project.id)
|
|
4540
|
-
.where('
|
|
5952
|
+
.where('url', '=', '/')
|
|
5953
|
+
.where('component_name', 'is', null)
|
|
4541
5954
|
.orderBy('created_at', 'asc')
|
|
4542
5955
|
.executeTakeFirst();
|
|
5956
|
+
const appScenario = homeScenario ||
|
|
5957
|
+
(await seedDb
|
|
5958
|
+
.selectFrom('editor_scenarios')
|
|
5959
|
+
.select(['id', 'name', 'type'])
|
|
5960
|
+
.where('project_id', '=', project.id)
|
|
5961
|
+
.where('type', '=', 'application')
|
|
5962
|
+
.orderBy('created_at', 'asc')
|
|
5963
|
+
.executeTakeFirst());
|
|
4543
5964
|
if (appScenario) {
|
|
4544
5965
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4545
5966
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4682,7 +6103,7 @@ const editorCommand = {
|
|
|
4682
6103
|
if (!needsAnalysis) {
|
|
4683
6104
|
try {
|
|
4684
6105
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4685
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
6106
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4686
6107
|
const db = getDatabase();
|
|
4687
6108
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4688
6109
|
if (backfillCount > 0) {
|
|
@@ -4721,7 +6142,7 @@ const editorCommand = {
|
|
|
4721
6142
|
.execute();
|
|
4722
6143
|
if (unresolved.length > 0) {
|
|
4723
6144
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4724
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
6145
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4725
6146
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4726
6147
|
let pfpResolved = 0;
|
|
4727
6148
|
for (const row of unresolved) {
|
|
@@ -4804,12 +6225,15 @@ const editorCommand = {
|
|
|
4804
6225
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4805
6226
|
const skipValidation = step === 2 && argv.feature;
|
|
4806
6227
|
if (!skipValidation) {
|
|
4807
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
6228
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4808
6229
|
if (stepError) {
|
|
4809
6230
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4810
6231
|
process.exit(1);
|
|
4811
6232
|
}
|
|
4812
6233
|
}
|
|
6234
|
+
printHandoffContext(root);
|
|
6235
|
+
// Track step progress automatically for provider handoff
|
|
6236
|
+
updateHandoffProgress(root, step);
|
|
4813
6237
|
switch (step) {
|
|
4814
6238
|
case 1: {
|
|
4815
6239
|
const feature = argv.feature || undefined;
|
|
@@ -4858,7 +6282,6 @@ const editorCommand = {
|
|
|
4858
6282
|
if (!auditOk) {
|
|
4859
6283
|
// checkAuditGate() already printed specific failure details above
|
|
4860
6284
|
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4861
|
-
console.error(chalk.yellow('DO NOT re-run the audit in a loop — fix the underlying issues first.'));
|
|
4862
6285
|
process.exit(1);
|
|
4863
6286
|
}
|
|
4864
6287
|
}
|