@codeyam/codeyam-cli 0.1.0-staging.9574237 → 0.1.0-staging.a2f381a
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/cli.js +24 -0
- package/codeyam-cli/src/cli.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js +51 -0
- package/codeyam-cli/src/commands/__tests__/editor.isolateArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js +55 -0
- package/codeyam-cli/src/commands/__tests__/editor.statePersistence.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +9 -9
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +39 -3
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +2497 -467
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js +23 -0
- package/codeyam-cli/src/commands/editorAnalyzeImportsArgs.js.map +1 -0
- package/codeyam-cli/src/commands/editorIsolateArgs.js +25 -0
- package/codeyam-cli/src/commands/editorIsolateArgs.js.map +1 -0
- package/codeyam-cli/src/commands/init.js +21 -0
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/commands/telemetry.js +37 -0
- package/codeyam-cli/src/commands/telemetry.js.map +1 -0
- package/codeyam-cli/src/data/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 +3174 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js +137 -0
- package/codeyam-cli/src/utils/__tests__/editorCaptureScenarioSeeding.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js +70 -0
- package/codeyam-cli/src/utils/__tests__/editorEntityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js +163 -4
- package/codeyam-cli/src/utils/__tests__/editorEntityHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js +67 -0
- package/codeyam-cli/src/utils/__tests__/editorGuardMiddleware.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +98 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js +1108 -0
- package/codeyam-cli/src/utils/__tests__/editorRoadmap.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js +190 -0
- package/codeyam-cli/src/utils/__tests__/editorScenarioSwitch.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +344 -7
- 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 +294 -2
- 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__/glossaryAdd.test.js +177 -0
- package/codeyam-cli/src/utils/__tests__/glossaryAdd.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__/journalCaptureStabilization.test.js +16 -1
- package/codeyam-cli/src/utils/__tests__/journalCaptureStabilization.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +57 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +180 -1
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js +84 -0
- package/codeyam-cli/src/utils/__tests__/screenshotHash.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js +159 -0
- package/codeyam-cli/src/utils/__tests__/telemetry.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +216 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/analysisRunner.js +36 -7
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +11 -1
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/backgroundServer.js +1 -1
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/designSystemShowcase.js +810 -0
- package/codeyam-cli/src/utils/designSystemShowcase.js.map +1 -0
- package/codeyam-cli/src/utils/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 +649 -10
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js +13 -7
- package/codeyam-cli/src/utils/editorEntityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/editorEntityHelpers.js +18 -3
- package/codeyam-cli/src/utils/editorEntityHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorGuard.js +36 -0
- package/codeyam-cli/src/utils/editorGuard.js.map +1 -0
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorRecapture.js +109 -0
- package/codeyam-cli/src/utils/editorRecapture.js.map +1 -0
- package/codeyam-cli/src/utils/editorRoadmap.js +574 -0
- package/codeyam-cli/src/utils/editorRoadmap.js.map +1 -0
- package/codeyam-cli/src/utils/editorScenarioSwitch.js +39 -2
- package/codeyam-cli/src/utils/editorScenarioSwitch.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +181 -17
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +69 -16
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +39 -5
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/envFile.js +90 -0
- package/codeyam-cli/src/utils/envFile.js.map +1 -0
- package/codeyam-cli/src/utils/fileWatcher.js +38 -0
- package/codeyam-cli/src/utils/fileWatcher.js.map +1 -1
- package/codeyam-cli/src/utils/glossaryAdd.js +74 -0
- package/codeyam-cli/src/utils/glossaryAdd.js.map +1 -0
- package/codeyam-cli/src/utils/handoffContext.js +257 -0
- package/codeyam-cli/src/utils/handoffContext.js.map +1 -0
- package/codeyam-cli/src/utils/install-skills.js +41 -6
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
- package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
- package/codeyam-cli/src/utils/queue/job.js +35 -6
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
- package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js +4 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -1
- package/codeyam-cli/src/utils/scenariosManifest.js +66 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/screenshotHash.js +26 -0
- package/codeyam-cli/src/utils/screenshotHash.js.map +1 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +9 -0
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/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/telemetry.js +106 -0
- package/codeyam-cli/src/utils/telemetry.js.map +1 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js +22 -0
- package/codeyam-cli/src/utils/telemetryMiddleware.js.map +1 -0
- package/codeyam-cli/src/utils/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 +68 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +442 -9
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +190 -21
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js +135 -0
- package/codeyam-cli/src/webserver/__tests__/stripClaudeCommand.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +22 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/database.js.map +1 -1
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
- package/codeyam-cli/src/webserver/backgroundServer.js +42 -57
- package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CzTDWkF2.js → CopyButton-DTBZZfSk.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-BFbq6iFk.js → EntityItem-BxclONWq.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-B6OMi58N.js → EntityTypeIcon-BsnEOJZ_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-DuYodzo1.js → InlineSpinner-ByaELMbv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CXo9EeCl.js → InteractivePreview-6WjVfhxX.js} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-DYCNb2It.js → LibraryFunctionPreview-ChX-Hp7W.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-CZgY3sxX.js → LogViewer-C-9zQdXg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-Bs2_Oua4.js +36 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CnYYwRDw.js → ReportIssueModal-DQsceHVv.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-CDoF7ZpU.js → SafeScreenshot-DThcm_9M.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DrnfvaLL.js → ScenarioViewer-Cl4oOA3A.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-CIil5-gb.js +34 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ViewportInspectBar-DRKR9T0U.js → ViewportInspectBar-BqkA9zyZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{_index-ClR-g3tY.js → _index-DnOgyseQ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-DTH6ydEA.js → activity.(_tab)-DqM9hbNE.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{addon-web-links-74hnHF59.js → addon-web-links-C58dYPwR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{agent-transcripts-B8CYhCO9.js → agent-transcripts-B8NCeOrm.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-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-recapture-stale-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-roadmap-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-save-scenario-data-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-schema-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-verify-routes-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.interactive-switch-scenario-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{book-open-CLaoh4ac.js → book-open-BFSIqZgO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-BZ2DZxbW.js → chevron-down-B9fDzFVh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/chunk-UVKPFVEO-Bmq2apuh.js +43 -0
- package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CT4unAk-.js → circle-check-DLPObLUx.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{copy-zK0B6Nu-.js → copy-DXEmO0TD.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-DJB0YQJL.js → createLucideIcon-BwyFiRot.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-Coe5NhbS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-CkXFP_i-.js → dev.empty-iRhRIFlp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-BZPBzV73.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-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._-BqAN7hyG.js → entity._sha._-pc-vc6wO.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-BOi8kpwd.js → entity._sha.scenarios._scenarioId.dev-C8AyYgYT.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-Dg1NhIms.js → entity._sha.scenarios._scenarioId.fullscreen-DziaVQX1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-CJX6kkkV.js → entity._sha_.create-scenario-BTcpgIpC.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-BhVjZhKg.js → entity._sha_.edit._scenarioId-D_O_ajfZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entry.client-_gzKltPN.js → entry.client-j1Vi0bco.js} +6 -6
- package/codeyam-cli/src/webserver/build/client/assets/{files-CV_17tZS.js → files-kuny2Q_s.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{git-D-YXmMbR.js → git-DgCZPMie.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-L-aUIeux.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{index-CCrgCshv.js → index-BliGSSpl.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-Blo6EK8G.js → index-SqjQKTdH.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{index-BsX0F-9C.js → index-vyrZD2g4.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{labs-Byazq8Pv.js → labs-c3yLxSEp.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-DVQ0oHR7.js → loader-circle-D-q28GLF.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-79d0d81a.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{memory-b-VmA2Vj.js → memory-CEWIUC4t.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{pause-DGcndCAa.js → pause-BP6fitdh.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-L2V0jea7.js +80 -0
- package/codeyam-cli/src/webserver/build/client/assets/{search-C0Uw0bcK.js → search-BooqacKS.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{settings-OoNgHIfW.js → settings-BM0nbryO.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{simulations-Bcemfu8a.js → simulations-ovy6FjRY.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{terminal-BgMmG7R9.js → terminal-DHemCJIs.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-Cs87hJYK.js → triangle-alert-D87ekDl8.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-BR3Rs7JY.js → useCustomSizes-Dk0Tciqg.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-C8QvIe05.js +2 -0
- package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-BermyNU5.js → useReportContext-jkCytuYz.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{useToast-a_QN_W9_.js → useToast-BgqkixU9.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-QgInFGdU.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-CHSrVJtC.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 +388 -26
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +65 -8
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/scripts/journalCapture.ts +53 -0
- package/codeyam-cli/src/webserver/server.js +151 -14
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +253 -41
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/__tests__/editor-step-hook.prompt-capture.test.ts +118 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +2 -0
- package/codeyam-cli/templates/codeyam-editor-codex.md +61 -0
- package/codeyam-cli/templates/codeyam-editor-gemini.md +59 -0
- package/codeyam-cli/templates/codeyam-editor-reference.md +216 -0
- package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
- package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
- package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
- package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
- package/codeyam-cli/templates/editor-step-hook.py +93 -46
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +204 -5
- package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +6 -3
- package/codeyam-cli/templates/expo-react-native/app/index.tsx +36 -0
- package/codeyam-cli/templates/expo-react-native/app.json +11 -0
- package/codeyam-cli/templates/expo-react-native/babel.config.js +1 -0
- package/codeyam-cli/templates/expo-react-native/gitignore +2 -0
- package/codeyam-cli/templates/expo-react-native/global.css +7 -0
- package/codeyam-cli/templates/expo-react-native/lib/theme.ts +73 -0
- package/codeyam-cli/templates/expo-react-native/package.json +32 -16
- package/codeyam-cli/templates/expo-react-native/patches/expo-modules-autolinking+3.0.24.patch +29 -0
- package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/gitignore +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -1
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -1
- package/codeyam-cli/templates/seed-adapters/supabase.ts +271 -78
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +34 -1
- package/package.json +2 -1
- package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
- package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
- package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
- package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
- package/packages/ai/src/lib/astScopes/paths.js +12 -3
- package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +3 -2
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +9 -7
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
- package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
- package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/database/src/lib/loadAnalysis.js +7 -1
- package/packages/database/src/lib/loadAnalysis.js.map +1 -1
- package/packages/database/src/lib/loadEntity.js +5 -5
- package/packages/database/src/lib/loadEntity.js.map +1 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Df3UCi8k.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/chunk-JZWAC4HX-BBXArFPl.js +0 -43
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor._tab-DPw7NZHc.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-Dmg9cGK3.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-DBa7T2FK.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-Bqg9V6XV.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-422a3551.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/root-ue8uWVRS.js +0 -67
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BxxP_XF9.js +0 -2
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-DXQyOV0G.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/init-DL8vWZ6m.js +0 -10
- package/codeyam-cli/src/webserver/build/server/assets/server-build-BUKVjBSZ.js +0 -501
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/_layout.tsx +0 -33
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/index.tsx +0 -12
- package/codeyam-cli/templates/expo-react-native/app/(tabs)/settings.tsx +0 -12
|
@@ -13,35 +13,41 @@ import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
14
|
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
15
|
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
|
+
import { DESIGN_SYSTEMS } from "../data/designSystems.js";
|
|
16
17
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
18
|
import initCommand from "./init.js";
|
|
18
19
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
19
|
-
import { clearEditorState,
|
|
20
|
+
import { clearEditorState, validateStepTransition, backfillEntityShaOnScenarios, } from "../utils/editorScenarios.js";
|
|
20
21
|
import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } from "../utils/editorSeedAdapter.js";
|
|
21
22
|
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
22
23
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
23
24
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
25
|
+
import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
|
|
24
26
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
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 = {
|
|
29
33
|
1: 'Plan',
|
|
30
|
-
2: '
|
|
31
|
-
3: '
|
|
32
|
-
4: '
|
|
33
|
-
5: '
|
|
34
|
-
6: '
|
|
35
|
-
7: '
|
|
36
|
-
8: '
|
|
37
|
-
9: '
|
|
38
|
-
10: '
|
|
39
|
-
11: '
|
|
40
|
-
12: '
|
|
41
|
-
13: '
|
|
42
|
-
14: '
|
|
43
|
-
15: '
|
|
44
|
-
16: '
|
|
34
|
+
2: 'Prepare',
|
|
35
|
+
3: 'Prototype',
|
|
36
|
+
4: 'Verify Prototype',
|
|
37
|
+
5: 'Confirm',
|
|
38
|
+
6: 'Deconstruct',
|
|
39
|
+
7: 'Extract',
|
|
40
|
+
8: 'Glossary',
|
|
41
|
+
9: 'Analyze',
|
|
42
|
+
10: 'App Scenarios',
|
|
43
|
+
11: 'User Scenarios',
|
|
44
|
+
12: 'Verify',
|
|
45
|
+
13: 'Journal',
|
|
46
|
+
14: 'Review',
|
|
47
|
+
15: 'Present',
|
|
48
|
+
16: 'Commit',
|
|
49
|
+
17: 'Finalize',
|
|
50
|
+
18: 'Push',
|
|
45
51
|
};
|
|
46
52
|
const MIGRATION_STEP_LABELS = {
|
|
47
53
|
1: 'Survey',
|
|
@@ -119,11 +125,19 @@ function writeState(root, state) {
|
|
|
119
125
|
const dir = path.dirname(statePath);
|
|
120
126
|
fs.mkdirSync(dir, { recursive: true });
|
|
121
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
|
+
}
|
|
122
135
|
}
|
|
123
136
|
/**
|
|
124
137
|
* Clear the editor state (for starting a new feature).
|
|
125
|
-
* Does NOT clear the user prompt file —
|
|
126
|
-
*
|
|
138
|
+
* Does NOT clear the user prompt file — the prompt is cleared
|
|
139
|
+
* by the journal API after recording, and the hook captures
|
|
140
|
+
* a fresh prompt for the next feature on UserPromptSubmit.
|
|
127
141
|
*/
|
|
128
142
|
function clearState(root) {
|
|
129
143
|
clearEditorState(root);
|
|
@@ -177,6 +191,181 @@ function getProjectDimensions(root) {
|
|
|
177
191
|
return { defaultName: 'Desktop', names: [] };
|
|
178
192
|
}
|
|
179
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
|
+
}
|
|
180
369
|
/**
|
|
181
370
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
182
371
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -200,38 +389,149 @@ function checkbox(text) {
|
|
|
200
389
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
201
390
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
202
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
|
+
}
|
|
203
472
|
/**
|
|
204
473
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
205
474
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
206
475
|
*/
|
|
207
476
|
function printAppScenarioInstructions(pageName, route) {
|
|
477
|
+
const root = process.cwd();
|
|
478
|
+
const hasSeedAdapter = !!detectSeedAdapter(root);
|
|
208
479
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
209
|
-
console.log(chalk.dim(' Review existing scenarios —
|
|
210
|
-
console.log(chalk.dim('
|
|
211
|
-
|
|
212
|
-
console.log(
|
|
480
|
+
console.log(chalk.dim(' Review existing scenarios — enhance their seed data to exercise new features.'));
|
|
481
|
+
console.log(chalk.dim(' A rich scenario that exercises 5 features is better than 5 thin scenarios with one feature each.'));
|
|
482
|
+
console.log(chalk.dim(' Rename scenarios if their data scope has grown beyond the original name.'));
|
|
483
|
+
console.log();
|
|
484
|
+
console.log(chalk.bold.yellow('App scenarios vs component scenarios — these are DIFFERENT:'));
|
|
485
|
+
console.log(chalk.yellow(' App scenarios: show the FULL PAGE with seeded data at a real route (/, /library, etc.)'));
|
|
486
|
+
console.log(chalk.yellow(' Component scenarios: show ONE COMPONENT in isolation at /isolated-components/...'));
|
|
487
|
+
console.log(chalk.yellow(' This step is about APP scenarios. Do NOT set "componentName" — that makes it a component scenario.'));
|
|
488
|
+
console.log();
|
|
489
|
+
checkbox('Identify every page/route in the app and ensure each has app-level scenarios');
|
|
490
|
+
console.log(chalk.dim(" Check the app's router/entry points for all distinct pages"));
|
|
491
|
+
console.log(chalk.yellow(' Every page needs at least 2-3 app scenarios — not just the main page'));
|
|
213
492
|
if (pageName) {
|
|
214
493
|
console.log(chalk.dim(` Example: "${pageName} - Full Data", "${pageName} - Empty State"`));
|
|
215
494
|
}
|
|
216
495
|
else {
|
|
217
|
-
console.log(chalk.dim('
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
console.log(chalk.dim(
|
|
223
|
-
console.log(
|
|
224
|
-
|
|
225
|
-
console.log();
|
|
226
|
-
|
|
227
|
-
console.log(chalk.
|
|
228
|
-
console.log(chalk.
|
|
229
|
-
console.log(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
496
|
+
console.log(chalk.dim(' Example: "Page - Full Data", "Page - Empty State", "Page - Error State"'));
|
|
497
|
+
}
|
|
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();
|
|
503
|
+
checkbox('Register each scenario with type "application", the real page URL, and pageFilePath:');
|
|
504
|
+
console.log(chalk.dim(` codeyam editor register '{"name":"${pageName || 'Page'} - Full Data",`));
|
|
505
|
+
console.log(chalk.dim(` "type":"application","url":"${route || '/'}","pageFilePath":"src/path/to/Page.tsx",...}'`));
|
|
506
|
+
console.log(chalk.yellow(' Required fields: "type":"application", "url" (real route), "pageFilePath" (source file)'));
|
|
507
|
+
console.log(chalk.yellow(' Do NOT include "componentName" — that would make it a component scenario'));
|
|
508
|
+
console.log();
|
|
509
|
+
if (hasSeedAdapter) {
|
|
510
|
+
checkbox(chalk.bold.red('REQUIRED: Every app scenario MUST include "seed" data — without it the page will be empty!'));
|
|
511
|
+
console.log(chalk.yellow(' A seed adapter exists — you MUST include "seed":{...} in every app scenario registration.'));
|
|
512
|
+
console.log(chalk.yellow(' An app scenario without seed data will render an empty page (no database rows = nothing to show).'));
|
|
513
|
+
console.log(chalk.dim(' "seed" maps table names to arrays of rows: "seed":{"user":[{"id":1,"name":"Alice"}],"article":[...]}'));
|
|
514
|
+
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
const appCtx = getTechStackContext(root);
|
|
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
|
+
}
|
|
523
|
+
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
524
|
+
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
525
|
+
}
|
|
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();
|
|
233
533
|
checkbox('If the app uses auth, email, or other patterns: see FEATURE_PATTERNS.md for scenario guidance');
|
|
234
|
-
checkbox('After
|
|
534
|
+
checkbox('After registration, check the response for `clientErrors`');
|
|
235
535
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
236
536
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
237
537
|
}
|
|
@@ -267,6 +567,12 @@ function printExtractionPlanInstructions() {
|
|
|
267
567
|
console.log(chalk.yellow(' Every component that renders multiple distinct sections should be split into sub-components.'));
|
|
268
568
|
console.log(chalk.yellow(' If a component has N visually distinct parts, it should compose N sub-components.'));
|
|
269
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();
|
|
270
576
|
console.log(chalk.bold('Checklist:'));
|
|
271
577
|
checkbox('Read `.codeyam/glossary.json` — note reusable functions/components');
|
|
272
578
|
checkbox('Read EVERY file used by this page/feature');
|
|
@@ -277,6 +583,12 @@ function printExtractionPlanInstructions() {
|
|
|
277
583
|
console.log(chalk.yellow(' Functions: data transforms, calculations, formatting, validation,'));
|
|
278
584
|
console.log(chalk.yellow(' API response shaping, any logic that is not directly about rendering'));
|
|
279
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'));
|
|
280
592
|
console.log();
|
|
281
593
|
checkbox('Write a numbered extraction plan listing EVERYTHING you will extract');
|
|
282
594
|
console.log(chalk.yellow(' The end state: every page file is ONLY imports + component composition.'));
|
|
@@ -287,17 +599,34 @@ function printExtractionPlanInstructions() {
|
|
|
287
599
|
console.log(chalk.yellow(' — What it is (component, function, hook)'));
|
|
288
600
|
console.log(chalk.yellow(' — Where it currently lives (source file + approximate lines)'));
|
|
289
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.'));
|
|
290
611
|
console.log();
|
|
291
|
-
console.log(chalk.dim('Present the numbered plan, then proceed to step
|
|
612
|
+
console.log(chalk.dim('Present the numbered plan, then proceed to step 7 to execute it.'));
|
|
292
613
|
}
|
|
293
614
|
/**
|
|
294
615
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
295
616
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
296
617
|
*/
|
|
297
|
-
function printComponentCaptureInstructions() {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
}
|
|
301
630
|
checkbox('For each visual component:');
|
|
302
631
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
303
632
|
console.log(chalk.dim(' — Props/interface'));
|
|
@@ -308,29 +637,48 @@ function printComponentCaptureInstructions() {
|
|
|
308
637
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
309
638
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
310
639
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
}
|
|
313
648
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
314
649
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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'));
|
|
319
662
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
320
663
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
321
664
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
322
665
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
323
666
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
324
667
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
325
|
-
console.log(chalk.dim(` "url":"/
|
|
668
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
326
669
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
327
|
-
console.log(chalk.
|
|
670
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
671
|
+
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
328
672
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
329
673
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
330
674
|
console.log(chalk.yellow(' 5. IMPORTANT: Check the register response for `clientErrors` array'));
|
|
331
675
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the isolation route and re-register'));
|
|
332
676
|
console.log(chalk.yellow(' Fix client errors and re-register before moving on'));
|
|
333
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'));
|
|
334
682
|
}
|
|
335
683
|
/**
|
|
336
684
|
* Print a section header.
|
|
@@ -344,13 +692,13 @@ function stepHeader(step, title, feature) {
|
|
|
344
692
|
console.log();
|
|
345
693
|
}
|
|
346
694
|
/**
|
|
347
|
-
* Print a colored progress tracker showing all
|
|
695
|
+
* Print a colored progress tracker showing all 18 steps.
|
|
348
696
|
* Steps before `current` are green ✓, `current` is bold cyan →, future steps are dim ○.
|
|
349
697
|
*/
|
|
350
698
|
function printProgressTracker(current) {
|
|
351
699
|
console.log();
|
|
352
700
|
console.log(chalk.dim(' ┌─────────────────────────────────────┐'));
|
|
353
|
-
for (let i = 1; i <=
|
|
701
|
+
for (let i = 1; i <= 18; i++) {
|
|
354
702
|
const label = STEP_LABELS[i];
|
|
355
703
|
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
356
704
|
const content = `${num}. ${label.padEnd(28)}`;
|
|
@@ -371,9 +719,33 @@ function printProgressTracker(current) {
|
|
|
371
719
|
*
|
|
372
720
|
* Options:
|
|
373
721
|
* - confirm: true → step requires user confirmation before proceeding (steps 1, 3, 11)
|
|
722
|
+
* - feature: string → feature name for task directive
|
|
374
723
|
*/
|
|
375
724
|
function stopGate(current, opts) {
|
|
376
|
-
|
|
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
|
+
}
|
|
377
749
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
378
750
|
console.log();
|
|
379
751
|
console.log(chalk.red('Complete each checklist item above before proceeding to the next step.'));
|
|
@@ -382,20 +754,12 @@ function stopGate(current, opts) {
|
|
|
382
754
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
383
755
|
}
|
|
384
756
|
console.log();
|
|
385
|
-
|
|
386
|
-
printProgressTracker(current);
|
|
387
|
-
console.log();
|
|
388
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
389
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
390
|
-
console.log();
|
|
391
|
-
if (current < 16) {
|
|
757
|
+
if (current < totalSteps) {
|
|
392
758
|
console.log(chalk.green('When done, run: ') +
|
|
393
759
|
chalk.bold(`codeyam editor ${current + 1}`));
|
|
394
760
|
}
|
|
395
761
|
else {
|
|
396
|
-
console.log(chalk.green('Feature complete!
|
|
397
|
-
chalk.bold('codeyam editor 1') +
|
|
398
|
-
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'));
|
|
399
763
|
}
|
|
400
764
|
console.log();
|
|
401
765
|
}
|
|
@@ -410,34 +774,36 @@ function printResumptionHeader(step) {
|
|
|
410
774
|
'Check if plan was already written to context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
411
775
|
],
|
|
412
776
|
2: ['Check if project files already exist:\n `ls package.json src/`'],
|
|
413
|
-
3: ['
|
|
414
|
-
4: [
|
|
777
|
+
3: ['Re-check is safe — just re-run the step'],
|
|
778
|
+
4: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
779
|
+
5: ['This is a confirmation step — just re-present to user'],
|
|
780
|
+
6: [
|
|
415
781
|
'Check if extraction plan already exists in context file:\n `cat .codeyam/editor-mode-context.md`',
|
|
416
782
|
],
|
|
417
|
-
|
|
783
|
+
7: [
|
|
418
784
|
'Check if components/functions already extracted:\n `ls src/components/ src/lib/`',
|
|
419
785
|
],
|
|
420
|
-
|
|
786
|
+
8: [
|
|
421
787
|
'Check if glossary already populated:\n `cat .codeyam/glossary.json`',
|
|
422
788
|
],
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
789
|
+
9: ['Check if isolation routes and registered scenarios already exist'],
|
|
790
|
+
10: ['Check existing scenarios:\n `codeyam editor scenarios`'],
|
|
791
|
+
11: [
|
|
426
792
|
'Check existing user-persona scenarios:\n `codeyam editor scenarios`',
|
|
427
793
|
],
|
|
428
|
-
|
|
429
|
-
|
|
794
|
+
12: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
795
|
+
13: [
|
|
430
796
|
'Check if a journal entry already exists for this feature:\n `codeyam editor journal-list`',
|
|
431
797
|
'If an entry exists, use PATCH to update it — do NOT create a new one',
|
|
432
798
|
],
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
799
|
+
14: ['Re-verify is safe to repeat — just re-run the checks'],
|
|
800
|
+
15: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
801
|
+
16: ['Check if commit already made:\n `git log --oneline -3`'],
|
|
802
|
+
17: [
|
|
437
803
|
'Check if journal was already updated with commit SHA:\n `codeyam editor journal-list`',
|
|
438
804
|
'Check if commit was already amended:\n `git log --oneline -3`',
|
|
439
805
|
],
|
|
440
|
-
|
|
806
|
+
18: [
|
|
441
807
|
'Check if already pushed:\n `git remote -v && git log --oneline origin/HEAD..HEAD 2>/dev/null`',
|
|
442
808
|
],
|
|
443
809
|
};
|
|
@@ -544,7 +910,7 @@ function parseDebugTargets(target) {
|
|
|
544
910
|
}
|
|
545
911
|
if (entry.startsWith('step-')) {
|
|
546
912
|
const num = parseInt(entry.replace('step-', ''), 10);
|
|
547
|
-
if (!isNaN(num) && num >= 1 && num <=
|
|
913
|
+
if (!isNaN(num) && num >= 1 && num <= 18) {
|
|
548
914
|
valid.add(`step-${num}`);
|
|
549
915
|
continue;
|
|
550
916
|
}
|
|
@@ -630,11 +996,17 @@ function printSetup(root) {
|
|
|
630
996
|
console.log();
|
|
631
997
|
// ── Design System ────────────────────────────────────────────────
|
|
632
998
|
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
633
|
-
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."'));
|
|
634
1000
|
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
635
|
-
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"'));
|
|
636
1008
|
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
637
|
-
console.log(chalk.yellow(' Option
|
|
1009
|
+
console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
|
|
638
1010
|
console.log(chalk.dim(' → Skip'));
|
|
639
1011
|
console.log();
|
|
640
1012
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -655,8 +1027,8 @@ function printSetup(root) {
|
|
|
655
1027
|
console.log(chalk.bold('Tech Stack Selection:'));
|
|
656
1028
|
console.log(chalk.dim(' Based on the selected formats, present ONLY the matching tech stacks.'));
|
|
657
1029
|
console.log(chalk.dim(' A stack matches if ANY of the user\'s selected formats appears in its "Supports" list.'));
|
|
658
|
-
console.log(chalk.dim('
|
|
659
|
-
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.'));
|
|
660
1032
|
console.log();
|
|
661
1033
|
console.log(chalk.bold(' Available Tech Stacks:'));
|
|
662
1034
|
for (const stack of TECH_STACKS) {
|
|
@@ -689,7 +1061,7 @@ function printSetup(root) {
|
|
|
689
1061
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
690
1062
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
691
1063
|
console.log();
|
|
692
|
-
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).'));
|
|
693
1065
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
694
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}}'`));
|
|
695
1067
|
console.log();
|
|
@@ -761,37 +1133,39 @@ function printCycleOverview(root, state) {
|
|
|
761
1133
|
console.log();
|
|
762
1134
|
console.log(chalk.green('Continue with: ') +
|
|
763
1135
|
chalk.bold(`codeyam editor ${state.step}`));
|
|
764
|
-
console.log(chalk.dim('Or run ')
|
|
765
|
-
chalk.bold('codeyam editor 1') +
|
|
766
|
-
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)'));
|
|
767
1137
|
console.log();
|
|
768
1138
|
console.log(chalk.yellow('If the user reports a bug or requests any change, run: ') +
|
|
769
1139
|
chalk.bold('codeyam editor change'));
|
|
770
1140
|
console.log(chalk.yellow('This applies even after committing — always use the change workflow.'));
|
|
771
1141
|
}
|
|
772
1142
|
else {
|
|
773
|
-
console.log('Each feature follows
|
|
1143
|
+
console.log('Each feature follows 18 steps. You MUST run each command in order:');
|
|
774
1144
|
console.log();
|
|
775
|
-
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')}
|
|
776
|
-
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('
|
|
777
|
-
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('
|
|
778
|
-
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('
|
|
779
|
-
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('
|
|
780
|
-
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('
|
|
781
|
-
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('
|
|
782
|
-
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('
|
|
783
|
-
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('
|
|
784
|
-
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('
|
|
785
|
-
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('
|
|
786
|
-
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('
|
|
787
|
-
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('
|
|
788
|
-
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('
|
|
789
|
-
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('
|
|
790
|
-
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('
|
|
1145
|
+
console.log(` ${chalk.bold.yellow(' 1')} ${chalk.bold('Plan')} — Plan the feature, confirm with user`);
|
|
1146
|
+
console.log(` ${chalk.bold.yellow(' 2')} ${chalk.bold('Prepare')} — Set up project and dependencies`);
|
|
1147
|
+
console.log(` ${chalk.bold.yellow(' 3')} ${chalk.bold('Prototype')} — Build a working prototype fast`);
|
|
1148
|
+
console.log(` ${chalk.bold.yellow(' 4')} ${chalk.bold('Verify Prototype')} — Verify prototype works correctly`);
|
|
1149
|
+
console.log(` ${chalk.bold.yellow(' 5')} ${chalk.bold('Confirm')} — Confirm prototype with user`);
|
|
1150
|
+
console.log(` ${chalk.bold.yellow(' 6')} ${chalk.bold('Deconstruct')} — Read code, plan all extractions`);
|
|
1151
|
+
console.log(` ${chalk.bold.yellow(' 7')} ${chalk.bold('Extract')} — TDD extraction of functions + components`);
|
|
1152
|
+
console.log(` ${chalk.bold.yellow(' 8')} ${chalk.bold('Glossary')} — Record functions in glossary`);
|
|
1153
|
+
console.log(` ${chalk.bold.yellow(' 9')} ${chalk.bold('Analyze')} — Analyze and verify components`);
|
|
1154
|
+
console.log(` ${chalk.bold.yellow('10')} ${chalk.bold('App Scenarios')} — Create app-level scenarios`);
|
|
1155
|
+
console.log(` ${chalk.bold.yellow('11')} ${chalk.bold('User Scenarios')} — Create user-persona scenarios`);
|
|
1156
|
+
console.log(` ${chalk.bold.yellow('12')} ${chalk.bold('Verify')} — Review screenshots, check for errors`);
|
|
1157
|
+
console.log(` ${chalk.bold.yellow('13')} ${chalk.bold('Journal')} — Create/update journal entry`);
|
|
1158
|
+
console.log(` ${chalk.bold.yellow('14')} ${chalk.bold('Review')} — Verify screenshots and audit`);
|
|
1159
|
+
console.log(` ${chalk.bold.yellow('15')} ${chalk.bold('Present')} — Present summary, get approval`);
|
|
1160
|
+
console.log(` ${chalk.bold.yellow('16')} ${chalk.bold('Commit')} — Commit all changes`);
|
|
1161
|
+
console.log(` ${chalk.bold.yellow('17')} ${chalk.bold('Finalize')} — Journal update + amend commit`);
|
|
1162
|
+
console.log(` ${chalk.bold.yellow('18')} ${chalk.bold('Push')} — Push to remote`);
|
|
791
1163
|
console.log();
|
|
792
|
-
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)');
|
|
793
1166
|
console.log(chalk.dim(' If the user already described what they want, pass it: codeyam editor 1 --prompt "their message"'));
|
|
794
1167
|
}
|
|
1168
|
+
console.log(chalk.dim('If stuck on CLI commands, scenarios, or debugging, read: .codeyam/docs/editor-reference.md'));
|
|
795
1169
|
console.log();
|
|
796
1170
|
}
|
|
797
1171
|
// ─── Step 1: Plan ─────────────────────────────────────────────────────
|
|
@@ -846,6 +1220,15 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
846
1220
|
console.log(chalk.dim(' -H "Content-Type: application/json" \\'));
|
|
847
1221
|
console.log(chalk.dim(' -d \'{"projectTitle":"My App","projectDescription":"A short description of what this app does"}\''));
|
|
848
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();
|
|
849
1232
|
const designSystem = readDesignSystem(root);
|
|
850
1233
|
if (designSystem) {
|
|
851
1234
|
console.log(chalk.bold.magenta('Design System (from .codeyam/design-system.md):'));
|
|
@@ -859,6 +1242,8 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
859
1242
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
860
1243
|
chalk.dim(' — user describes changes, you revise the plan, then re-present'));
|
|
861
1244
|
console.log();
|
|
1245
|
+
checkboxHandoff();
|
|
1246
|
+
console.log();
|
|
862
1247
|
console.log(chalk.dim('This step is for understanding user goals and getting buy-in. Code comes in Step 2.'));
|
|
863
1248
|
console.log();
|
|
864
1249
|
console.log(chalk.bold.red('━━━ STOP ━━━'));
|
|
@@ -867,18 +1252,12 @@ function printStep1(root, feature, options, userPrompt) {
|
|
|
867
1252
|
console.log();
|
|
868
1253
|
console.log(chalk.yellow('Wait for user confirmation before moving on.'));
|
|
869
1254
|
console.log();
|
|
870
|
-
console.log(chalk.bold.yellow('Present the following progress tracker to the user (copy it verbatim):'));
|
|
871
|
-
printProgressTracker(1);
|
|
872
|
-
console.log();
|
|
873
|
-
console.log(chalk.yellow('For the CURRENT step (→), show each checklist item with ✓ (done) or ✗ (skipped + reason).'));
|
|
874
|
-
console.log(chalk.yellow('If any items are ✗, explain why and ask if the user wants to address them.'));
|
|
875
|
-
console.log();
|
|
876
1255
|
console.log(chalk.green('When done, run: ') +
|
|
877
1256
|
chalk.bold('codeyam editor 2 --feature "Feature Name"'));
|
|
878
1257
|
console.log(chalk.dim(' Replace "Feature Name" with a short title for what you just described.'));
|
|
879
1258
|
console.log();
|
|
880
1259
|
}
|
|
881
|
-
// ─── Step 2:
|
|
1260
|
+
// ─── Step 2: Prepare ──────────────────────────────────────────────────
|
|
882
1261
|
function printStep2(root, feature) {
|
|
883
1262
|
const port = getServerPort();
|
|
884
1263
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
@@ -895,51 +1274,167 @@ function printStep2(root, feature) {
|
|
|
895
1274
|
appFormats: prevState?.appFormats,
|
|
896
1275
|
techStackId: prevState?.techStackId,
|
|
897
1276
|
});
|
|
898
|
-
logEvent(root, 'step', { step: 2, label: '
|
|
899
|
-
stepHeader(2, '
|
|
1277
|
+
logEvent(root, 'step', { step: 2, label: 'Prepare', feature });
|
|
1278
|
+
stepHeader(2, 'Prepare', feature);
|
|
900
1279
|
if (isResuming) {
|
|
901
1280
|
printResumptionHeader(2);
|
|
902
1281
|
}
|
|
903
|
-
console.log('
|
|
1282
|
+
console.log('Get the project ready to build.');
|
|
904
1283
|
console.log();
|
|
1284
|
+
const ctx = getTechStackContext(root);
|
|
905
1285
|
// If no project exists yet, include scaffolding instructions first
|
|
906
1286
|
if (!projectExists) {
|
|
907
1287
|
console.log(chalk.bold('Scaffold the project:'));
|
|
908
1288
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
909
|
-
|
|
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
|
+
}
|
|
910
1298
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
911
1299
|
console.log();
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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();
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
1361
|
+
checkbox('List existing scenarios: `codeyam editor scenarios`');
|
|
1362
|
+
console.log(chalk.dim(' Review existing scenarios to find seed data that best demonstrates the feature.'));
|
|
1363
|
+
console.log(chalk.dim(" Don't create throwaway seed data — use what's already been curated."));
|
|
1364
|
+
checkbox('Pick the scenario with seed data most relevant to this feature');
|
|
1365
|
+
console.log(chalk.dim(" Think: which existing data state best exercises the feature you're building?"));
|
|
1366
|
+
console.log(chalk.dim(' A scenario with diverse, realistic data is usually the best starting point.'));
|
|
1367
|
+
checkbox('Load that scenario to seed the database and preview it:');
|
|
1368
|
+
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1369
|
+
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
1370
|
+
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
931
1371
|
console.log();
|
|
932
|
-
|
|
1372
|
+
printDataStructureInstructions();
|
|
933
1373
|
}
|
|
1374
|
+
console.log();
|
|
1375
|
+
printServiceRecordingReminder(port);
|
|
1376
|
+
console.log();
|
|
1377
|
+
checkboxHandoff();
|
|
1378
|
+
stopGate(2);
|
|
1379
|
+
}
|
|
1380
|
+
// ─── Step 3: Prototype ────────────────────────────────────────────────
|
|
1381
|
+
function printStep3(root, feature) {
|
|
1382
|
+
const port = getServerPort();
|
|
1383
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1384
|
+
const projectExists = hasProject(root);
|
|
1385
|
+
const prevState = readState(root);
|
|
1386
|
+
const isResuming = prevState?.step === 3;
|
|
1387
|
+
const now = new Date().toISOString();
|
|
1388
|
+
writeState(root, {
|
|
1389
|
+
feature,
|
|
1390
|
+
step: 3,
|
|
1391
|
+
label: STEP_LABELS[3],
|
|
1392
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1393
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1394
|
+
appFormats: prevState?.appFormats,
|
|
1395
|
+
techStackId: prevState?.techStackId,
|
|
1396
|
+
});
|
|
1397
|
+
logEvent(root, 'step', { step: 3, label: 'Prototype', feature });
|
|
1398
|
+
stepHeader(3, 'Prototype', feature);
|
|
1399
|
+
if (isResuming) {
|
|
1400
|
+
printResumptionHeader(3);
|
|
1401
|
+
}
|
|
1402
|
+
const ctx = getTechStackContext(root);
|
|
1403
|
+
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1404
|
+
console.log();
|
|
934
1405
|
console.log(chalk.bold('Checklist:'));
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
+
}
|
|
1435
|
+
}
|
|
941
1436
|
checkbox('Verify the dev server shows the changes');
|
|
942
|
-
checkbox(
|
|
1437
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
943
1438
|
// Responsive design guidance when building a mobile-responsive web app
|
|
944
1439
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
945
1440
|
console.log();
|
|
@@ -956,16 +1451,33 @@ function printStep2(root, feature) {
|
|
|
956
1451
|
console.log();
|
|
957
1452
|
console.log(designSystem);
|
|
958
1453
|
console.log();
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|
+
}
|
|
969
1481
|
}
|
|
970
1482
|
console.log();
|
|
971
1483
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -976,16 +1488,44 @@ function printStep2(root, feature) {
|
|
|
976
1488
|
console.log(chalk.cyan(' If you build a NEW page (e.g., /drinks/[id]), navigate the preview there:'));
|
|
977
1489
|
console.log(chalk.cyan(` codeyam editor preview '{"path":"/drinks/1","dimension":"${dim}"}'`));
|
|
978
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.'));
|
|
1493
|
+
console.log();
|
|
1494
|
+
printServiceRecordingReminder(port);
|
|
1495
|
+
console.log();
|
|
1496
|
+
checkboxHandoff();
|
|
1497
|
+
stopGate(3);
|
|
1498
|
+
}
|
|
1499
|
+
// ─── Step 4: Verify Prototype ─────────────────────────────────────────
|
|
1500
|
+
function printStep4(root, feature) {
|
|
1501
|
+
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1502
|
+
const prevState = readState(root);
|
|
1503
|
+
const isResuming = prevState?.step === 4;
|
|
1504
|
+
const now = new Date().toISOString();
|
|
1505
|
+
writeState(root, {
|
|
1506
|
+
feature,
|
|
1507
|
+
step: 4,
|
|
1508
|
+
label: STEP_LABELS[4],
|
|
1509
|
+
startedAt: isResuming ? prevState.startedAt : now,
|
|
1510
|
+
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1511
|
+
appFormats: prevState?.appFormats,
|
|
1512
|
+
techStackId: prevState?.techStackId,
|
|
1513
|
+
});
|
|
1514
|
+
logEvent(root, 'step', { step: 4, label: 'Verify Prototype', feature });
|
|
1515
|
+
stepHeader(4, 'Verify Prototype', feature);
|
|
1516
|
+
if (isResuming) {
|
|
1517
|
+
printResumptionHeader(4);
|
|
1518
|
+
}
|
|
1519
|
+
console.log('Verify everything works before presenting the prototype.');
|
|
979
1520
|
console.log();
|
|
980
1521
|
console.log(chalk.bold('Verify the dev server:'));
|
|
981
|
-
console.log(chalk.dim(
|
|
982
|
-
console.log(chalk.dim(
|
|
983
|
-
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"]}'`));
|
|
984
1524
|
console.log();
|
|
985
1525
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
986
1526
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
987
|
-
checkbox('Verify
|
|
988
|
-
|
|
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.'));
|
|
989
1529
|
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
990
1530
|
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
991
1531
|
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
@@ -996,6 +1536,14 @@ function printStep2(root, feature) {
|
|
|
996
1536
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
997
1537
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
998
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
|
+
}
|
|
999
1547
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1000
1548
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1001
1549
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1003,32 +1551,36 @@ function printStep2(root, feature) {
|
|
|
1003
1551
|
console.log(chalk.dim(' A new clone should work with just: git clone → npm run setup → npm run dev'));
|
|
1004
1552
|
console.log();
|
|
1005
1553
|
console.log(chalk.dim('Focus on building the prototype. Scenarios and refactoring happen in later steps.'));
|
|
1006
|
-
|
|
1554
|
+
console.log();
|
|
1555
|
+
checkboxHandoff();
|
|
1556
|
+
stopGate(4);
|
|
1007
1557
|
}
|
|
1008
|
-
// ─── Step
|
|
1009
|
-
function
|
|
1558
|
+
// ─── Step 5: Confirm ──────────────────────────────────────────────────
|
|
1559
|
+
function printStep5(root, feature) {
|
|
1010
1560
|
const port = getServerPort();
|
|
1011
1561
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1012
1562
|
const prevState = readState(root);
|
|
1013
|
-
const isResuming = prevState?.step ===
|
|
1563
|
+
const isResuming = prevState?.step === 5;
|
|
1014
1564
|
const now = new Date().toISOString();
|
|
1015
1565
|
writeState(root, {
|
|
1016
1566
|
feature,
|
|
1017
|
-
step:
|
|
1018
|
-
label: STEP_LABELS[
|
|
1567
|
+
step: 5,
|
|
1568
|
+
label: STEP_LABELS[5],
|
|
1019
1569
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1020
1570
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1571
|
+
appFormats: prevState?.appFormats,
|
|
1572
|
+
techStackId: prevState?.techStackId,
|
|
1021
1573
|
});
|
|
1022
|
-
logEvent(root, 'step', { step:
|
|
1023
|
-
stepHeader(
|
|
1574
|
+
logEvent(root, 'step', { step: 5, label: 'Confirm', feature });
|
|
1575
|
+
stepHeader(5, 'Confirm', feature);
|
|
1024
1576
|
if (isResuming) {
|
|
1025
|
-
printResumptionHeader(
|
|
1577
|
+
printResumptionHeader(5);
|
|
1026
1578
|
}
|
|
1027
1579
|
console.log('Summarize what was built and get user confirmation.');
|
|
1028
1580
|
console.log();
|
|
1029
1581
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
1030
1582
|
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
1031
|
-
checkbox('Verify
|
|
1583
|
+
checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1032
1584
|
console.log();
|
|
1033
1585
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
1034
1586
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
@@ -1039,12 +1591,19 @@ function printStep3(root, feature) {
|
|
|
1039
1591
|
console.log(chalk.dim(' If there are errors, fix the underlying issue before presenting.'));
|
|
1040
1592
|
checkbox('Verify `hasContent=true` and `liveErrors=0` — do NOT ask the user to confirm if the preview is broken');
|
|
1041
1593
|
console.log();
|
|
1594
|
+
console.log(chalk.bold('Verify the captured user prompt:'));
|
|
1595
|
+
checkbox("Read `.codeyam/editor-user-prompt.txt` — this is the user's original feature request");
|
|
1596
|
+
checkbox('If the file is missing or does not match what the user actually asked for, write the correct prompt text to `.codeyam/editor-user-prompt.txt`');
|
|
1597
|
+
console.log(chalk.dim(" This must be the user's exact words, not a summary. It gets recorded in the journal."));
|
|
1598
|
+
console.log();
|
|
1042
1599
|
console.log(chalk.bold('Then present to the user:'));
|
|
1043
1600
|
checkbox('Summarize what was built (routes, components, data)');
|
|
1044
1601
|
checkbox(`Navigate the preview to the feature's primary page: \`codeyam editor preview '{"path":"/your-page","dimension":"${dim}"}'\``);
|
|
1045
1602
|
printDimensionGuidance(dim, dimNames);
|
|
1046
1603
|
console.log(chalk.dim(' The user needs to SEE the new feature. If you built a new page, navigate there.'));
|
|
1047
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.'));
|
|
1048
1607
|
console.log();
|
|
1049
1608
|
console.log(chalk.bold.cyan('Guide the user through interactive testing:'));
|
|
1050
1609
|
checkbox('Tell the user: "The preview is fully interactive — click around to test!"');
|
|
@@ -1052,80 +1611,115 @@ function printStep3(root, feature) {
|
|
|
1052
1611
|
checkbox('Ask the user: "Does everything work as expected?"');
|
|
1053
1612
|
console.log();
|
|
1054
1613
|
console.log(chalk.bold('Present a selection menu to the user (use AskUserQuestion with these EXACT option labels):'));
|
|
1055
|
-
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step
|
|
1614
|
+
console.log(chalk.green(' Option 1 label: "The live preview is displaying what I expected"') + chalk.dim(' — proceed to step 6'));
|
|
1056
1615
|
console.log(chalk.yellow(' Option 2 label: "I\'d like some changes"') +
|
|
1057
|
-
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor
|
|
1616
|
+
chalk.dim(' — make changes, refresh preview, re-run `codeyam editor 5`'));
|
|
1058
1617
|
console.log();
|
|
1059
1618
|
console.log(chalk.dim('Wait for user approval before moving on. Refactoring and scenarios happen in later steps.'));
|
|
1060
|
-
|
|
1619
|
+
console.log();
|
|
1620
|
+
checkboxHandoff();
|
|
1621
|
+
stopGate(5, { confirm: true });
|
|
1061
1622
|
}
|
|
1062
|
-
// ─── Step
|
|
1063
|
-
function
|
|
1623
|
+
// ─── Step 6: Deconstruct ──────────────────────────────────────────────
|
|
1624
|
+
function printStep6(root, feature) {
|
|
1064
1625
|
const prevState = readState(root);
|
|
1065
|
-
const isResuming = prevState?.step ===
|
|
1626
|
+
const isResuming = prevState?.step === 6;
|
|
1066
1627
|
const now = new Date().toISOString();
|
|
1067
1628
|
writeState(root, {
|
|
1068
1629
|
feature,
|
|
1069
|
-
step:
|
|
1070
|
-
label: STEP_LABELS[
|
|
1630
|
+
step: 6,
|
|
1631
|
+
label: STEP_LABELS[6],
|
|
1071
1632
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1072
1633
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1634
|
+
appFormats: prevState?.appFormats,
|
|
1635
|
+
techStackId: prevState?.techStackId,
|
|
1073
1636
|
});
|
|
1074
|
-
logEvent(root, 'step', { step:
|
|
1075
|
-
stepHeader(
|
|
1637
|
+
logEvent(root, 'step', { step: 6, label: 'Deconstruct', feature });
|
|
1638
|
+
stepHeader(6, 'Deconstruct', feature);
|
|
1076
1639
|
if (isResuming) {
|
|
1077
|
-
printResumptionHeader(
|
|
1640
|
+
printResumptionHeader(6);
|
|
1078
1641
|
}
|
|
1079
1642
|
console.log(chalk.bold('Goal: pages contain ONLY components. Components contain ONLY sub-components.'));
|
|
1080
|
-
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step
|
|
1643
|
+
console.log(chalk.yellow('This step is read and plan only. Code extraction happens in step 7.'));
|
|
1081
1644
|
console.log();
|
|
1082
1645
|
printExtractionPlanInstructions();
|
|
1083
|
-
|
|
1646
|
+
console.log();
|
|
1647
|
+
checkboxHandoff();
|
|
1648
|
+
stopGate(6);
|
|
1084
1649
|
}
|
|
1085
|
-
// ─── Step
|
|
1086
|
-
function
|
|
1650
|
+
// ─── Step 7: Extract ──────────────────────────────────────────────────
|
|
1651
|
+
function printStep7(root, feature) {
|
|
1087
1652
|
const port = getServerPort();
|
|
1088
1653
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1089
1654
|
const prevState = readState(root);
|
|
1090
|
-
const isResuming = prevState?.step ===
|
|
1655
|
+
const isResuming = prevState?.step === 7;
|
|
1091
1656
|
const now = new Date().toISOString();
|
|
1092
1657
|
writeState(root, {
|
|
1093
1658
|
feature,
|
|
1094
|
-
step:
|
|
1095
|
-
label: STEP_LABELS[
|
|
1659
|
+
step: 7,
|
|
1660
|
+
label: STEP_LABELS[7],
|
|
1096
1661
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1097
1662
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1663
|
+
appFormats: prevState?.appFormats,
|
|
1664
|
+
techStackId: prevState?.techStackId,
|
|
1098
1665
|
});
|
|
1099
|
-
logEvent(root, 'step', { step:
|
|
1100
|
-
stepHeader(
|
|
1666
|
+
logEvent(root, 'step', { step: 7, label: 'Extract', feature });
|
|
1667
|
+
stepHeader(7, 'Extract', feature);
|
|
1101
1668
|
if (isResuming) {
|
|
1102
|
-
printResumptionHeader(
|
|
1669
|
+
printResumptionHeader(7);
|
|
1103
1670
|
}
|
|
1104
|
-
|
|
1671
|
+
const ctx = getTechStackContext(root);
|
|
1672
|
+
console.log('Execute your extraction plan from step 6.');
|
|
1105
1673
|
console.log();
|
|
1106
1674
|
console.log(chalk.bold('Components:'));
|
|
1107
1675
|
checkbox('Extract each component from your plan into its own file');
|
|
1108
1676
|
checkbox('Page/route files must contain ZERO direct JSX — only imported components');
|
|
1109
1677
|
checkbox('Every component that renders multiple sections must be split into sub-components');
|
|
1110
|
-
console.log(chalk.dim(' No tests needed — visual verification happens in step
|
|
1678
|
+
console.log(chalk.dim(' No tests needed — visual verification happens in step 9'));
|
|
1111
1679
|
console.log();
|
|
1112
1680
|
console.log(chalk.bold('Library functions AND hooks (TDD):'));
|
|
1113
1681
|
checkbox('For each function/hook: write MULTIPLE failing tests FIRST, then extract to make them pass');
|
|
1114
1682
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1115
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'));
|
|
1116
1687
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
+
}
|
|
1695
|
+
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1119
1696
|
console.log();
|
|
1120
1697
|
console.log(chalk.bold('Recursive pass:'));
|
|
1121
1698
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1122
1699
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1123
|
-
console.log(chalk.yellow(
|
|
1700
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1124
1701
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1125
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();
|
|
1126
1720
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1127
1721
|
checkbox('Run all tests and verify they pass');
|
|
1128
|
-
checkbox(
|
|
1722
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1129
1723
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1130
1724
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1131
1725
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1133,93 +1727,117 @@ function printStep5(root, feature) {
|
|
|
1133
1727
|
console.log(chalk.dim('Reuse glossary functions when they fit naturally. Extract a new function when the use case diverges.'));
|
|
1134
1728
|
console.log();
|
|
1135
1729
|
console.log(chalk.dim('Focus on TDD for functions and extraction for components. Scenarios come in later steps.'));
|
|
1136
|
-
|
|
1730
|
+
console.log();
|
|
1731
|
+
checkboxHandoff();
|
|
1732
|
+
stopGate(7);
|
|
1137
1733
|
}
|
|
1138
|
-
// ─── Step
|
|
1139
|
-
function
|
|
1734
|
+
// ─── Step 8: Glossary ─────────────────────────────────────────────────
|
|
1735
|
+
function printStep8(root, feature) {
|
|
1140
1736
|
const prevState = readState(root);
|
|
1141
|
-
const isResuming = prevState?.step ===
|
|
1737
|
+
const isResuming = prevState?.step === 8;
|
|
1142
1738
|
const now = new Date().toISOString();
|
|
1143
1739
|
writeState(root, {
|
|
1144
1740
|
feature,
|
|
1145
|
-
step:
|
|
1146
|
-
label: STEP_LABELS[
|
|
1741
|
+
step: 8,
|
|
1742
|
+
label: STEP_LABELS[8],
|
|
1147
1743
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1148
1744
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1745
|
+
appFormats: prevState?.appFormats,
|
|
1746
|
+
techStackId: prevState?.techStackId,
|
|
1149
1747
|
});
|
|
1150
|
-
logEvent(root, 'step', { step:
|
|
1151
|
-
stepHeader(
|
|
1748
|
+
logEvent(root, 'step', { step: 8, label: 'Glossary', feature });
|
|
1749
|
+
stepHeader(8, 'Glossary', feature);
|
|
1152
1750
|
if (isResuming) {
|
|
1153
|
-
printResumptionHeader(
|
|
1751
|
+
printResumptionHeader(8);
|
|
1154
1752
|
}
|
|
1155
1753
|
console.log('Record all new functions/components in `.codeyam/glossary.json`.');
|
|
1156
1754
|
console.log();
|
|
1157
1755
|
printGlossaryInstructions(feature);
|
|
1158
1756
|
console.log();
|
|
1159
1757
|
console.log(chalk.dim('Focus on updating the glossary. Application code and scenarios come in later steps.'));
|
|
1160
|
-
stopGate(
|
|
1758
|
+
stopGate(8);
|
|
1161
1759
|
}
|
|
1162
|
-
// ─── Step
|
|
1163
|
-
function
|
|
1760
|
+
// ─── Step 9: Analyze ──────────────────────────────────────────────────
|
|
1761
|
+
function printStep9(root, feature) {
|
|
1164
1762
|
const port = getServerPort();
|
|
1165
1763
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1166
1764
|
const prevState = readState(root);
|
|
1167
|
-
const isResuming = prevState?.step ===
|
|
1765
|
+
const isResuming = prevState?.step === 9;
|
|
1168
1766
|
const now = new Date().toISOString();
|
|
1169
1767
|
writeState(root, {
|
|
1170
1768
|
feature,
|
|
1171
|
-
step:
|
|
1172
|
-
label: STEP_LABELS[
|
|
1769
|
+
step: 9,
|
|
1770
|
+
label: STEP_LABELS[9],
|
|
1173
1771
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1174
1772
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1773
|
+
appFormats: prevState?.appFormats,
|
|
1774
|
+
techStackId: prevState?.techStackId,
|
|
1175
1775
|
});
|
|
1176
|
-
logEvent(root, 'step', { step:
|
|
1177
|
-
stepHeader(
|
|
1776
|
+
logEvent(root, 'step', { step: 9, label: 'Analyze', feature });
|
|
1777
|
+
stepHeader(9, 'Analyze and Verify', feature);
|
|
1178
1778
|
if (isResuming) {
|
|
1179
|
-
printResumptionHeader(
|
|
1779
|
+
printResumptionHeader(9);
|
|
1180
1780
|
}
|
|
1181
1781
|
console.log('Verify visual components (via isolation routes) and library functions (via tests).');
|
|
1182
1782
|
console.log();
|
|
1183
1783
|
console.log(chalk.bold('Visual Components — Component Isolation:'));
|
|
1184
|
-
checkbox('List all files with new/modified visual components from step
|
|
1784
|
+
checkbox('List all files with new/modified visual components from step 7');
|
|
1185
1785
|
checkbox('Check existing scenarios: `codeyam editor scenarios`');
|
|
1186
1786
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1187
1787
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1188
1788
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1189
|
-
|
|
1789
|
+
const ctx9 = getTechStackContext(root);
|
|
1790
|
+
printComponentCaptureInstructions(root);
|
|
1190
1791
|
console.log();
|
|
1191
1792
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1192
|
-
checkbox('Run ALL test files created in step
|
|
1193
|
-
console.log(chalk.dim(
|
|
1793
|
+
checkbox('Run ALL test files created in step 7');
|
|
1794
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1194
1795
|
checkbox('Verify every test passes');
|
|
1195
1796
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1196
1797
|
console.log();
|
|
1197
1798
|
console.log(chalk.dim('Do not proceed until both component isolations and library tests pass.'));
|
|
1198
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();
|
|
1199
1809
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1200
|
-
console.log(chalk.red.bold(' The audit is a HARD GATE — step
|
|
1201
|
-
console.log(chalk.dim('
|
|
1810
|
+
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
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.'));
|
|
1202
1818
|
console.log();
|
|
1203
|
-
stopGate(
|
|
1819
|
+
stopGate(9);
|
|
1204
1820
|
}
|
|
1205
|
-
// ─── Step
|
|
1206
|
-
function
|
|
1821
|
+
// ─── Step 10: App Scenarios ────────────────────────────────────────────
|
|
1822
|
+
function printStep10(root, feature) {
|
|
1207
1823
|
const port = getServerPort();
|
|
1208
1824
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1209
1825
|
const prevState = readState(root);
|
|
1210
|
-
const isResuming = prevState?.step ===
|
|
1826
|
+
const isResuming = prevState?.step === 10;
|
|
1211
1827
|
const now = new Date().toISOString();
|
|
1212
1828
|
writeState(root, {
|
|
1213
1829
|
feature,
|
|
1214
|
-
step:
|
|
1215
|
-
label: STEP_LABELS[
|
|
1830
|
+
step: 10,
|
|
1831
|
+
label: STEP_LABELS[10],
|
|
1216
1832
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1217
1833
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1834
|
+
appFormats: prevState?.appFormats,
|
|
1835
|
+
techStackId: prevState?.techStackId,
|
|
1218
1836
|
});
|
|
1219
|
-
logEvent(root, 'step', { step:
|
|
1220
|
-
stepHeader(
|
|
1837
|
+
logEvent(root, 'step', { step: 10, label: 'App Scenarios', feature });
|
|
1838
|
+
stepHeader(10, 'App Scenarios', feature);
|
|
1221
1839
|
if (isResuming) {
|
|
1222
|
-
printResumptionHeader(
|
|
1840
|
+
printResumptionHeader(10);
|
|
1223
1841
|
}
|
|
1224
1842
|
console.log('Create app-level scenarios with rich data that robustly demonstrates this feature.');
|
|
1225
1843
|
console.log();
|
|
@@ -1233,6 +1851,7 @@ function printStep8(root, feature) {
|
|
|
1233
1851
|
console.log(chalk.cyan(' • Include realistic variety — different categories, lengths, statuses, counts'));
|
|
1234
1852
|
console.log(chalk.cyan(" • Populate optional fields the feature depends on (don't leave them empty)"));
|
|
1235
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'));
|
|
1236
1855
|
console.log(chalk.cyan(" • Think: would this data reveal a bug in the feature? If it's too simple, enrich it."));
|
|
1237
1856
|
console.log();
|
|
1238
1857
|
console.log(chalk.bold.cyan('When to create new vs reuse existing:'));
|
|
@@ -1241,13 +1860,19 @@ function printStep8(root, feature) {
|
|
|
1241
1860
|
console.log(chalk.cyan(" • New data states that can't coexist in one scenario (empty vs rich, error vs success) → new scenario"));
|
|
1242
1861
|
console.log(chalk.cyan(" • Don't duplicate — if an existing scenario can cover a state with richer data, enhance it instead"));
|
|
1243
1862
|
console.log();
|
|
1863
|
+
console.log(chalk.bold.cyan('Scenario naming — describe data states, not features:'));
|
|
1864
|
+
console.log(chalk.cyan(' • Name scenarios by what they represent: "Rich Data", "Empty", "Mobile", "Minimal"'));
|
|
1865
|
+
console.log(chalk.cyan(' • When enhancing a scenario with new feature data, update the name if it has grown beyond its original scope'));
|
|
1866
|
+
console.log(chalk.cyan(' • A single rich scenario exercising multiple features is more valuable than separate thin scenarios per feature'));
|
|
1867
|
+
console.log(chalk.cyan(' • Re-register with the same name to update; to rename, register with the new name'));
|
|
1868
|
+
console.log();
|
|
1244
1869
|
checkbox('Ensure scenarios clearly demonstrate what changed in this session');
|
|
1245
1870
|
console.log(chalk.dim(' If data models changed: update existing scenarios seed data to match'));
|
|
1246
1871
|
console.log(chalk.dim(' If UI changed: re-register existing scenarios so screenshots reflect the update'));
|
|
1247
1872
|
console.log(chalk.dim(' Add new scenarios only for genuinely new data states not covered by existing ones'));
|
|
1248
1873
|
printAppScenarioInstructions();
|
|
1249
1874
|
console.log();
|
|
1250
|
-
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step
|
|
1875
|
+
console.log(chalk.dim('Focus on creating and registering app-level scenarios. Code fixes happen in step 12 if needed.'));
|
|
1251
1876
|
console.log();
|
|
1252
1877
|
console.log(chalk.bold.cyan("Verify your work — screenshots don't lie:"));
|
|
1253
1878
|
console.log(chalk.cyan(' • View every captured screenshot. Does it show what the scenario name promises?'));
|
|
@@ -1258,43 +1883,45 @@ function printStep8(root, feature) {
|
|
|
1258
1883
|
console.log(chalk.bold.yellow('GATE: Before proceeding, run `codeyam editor scenario-coverage`'));
|
|
1259
1884
|
console.log(chalk.yellow(' This checks which existing scenarios have stale screenshots.'));
|
|
1260
1885
|
console.log(chalk.yellow(' Re-register every stale scenario listed until the check passes.'));
|
|
1261
|
-
stopGate(
|
|
1886
|
+
stopGate(10);
|
|
1262
1887
|
}
|
|
1263
|
-
// ─── Step
|
|
1264
|
-
function
|
|
1888
|
+
// ─── Step 11: User Scenarios ───────────────────────────────────────────
|
|
1889
|
+
function printStep11(root, feature) {
|
|
1265
1890
|
const port = getServerPort();
|
|
1266
1891
|
const prevState = readState(root);
|
|
1267
|
-
const isResuming = prevState?.step ===
|
|
1892
|
+
const isResuming = prevState?.step === 11;
|
|
1268
1893
|
const now = new Date().toISOString();
|
|
1269
1894
|
writeState(root, {
|
|
1270
1895
|
feature,
|
|
1271
|
-
step:
|
|
1272
|
-
label: STEP_LABELS[
|
|
1896
|
+
step: 11,
|
|
1897
|
+
label: STEP_LABELS[11],
|
|
1273
1898
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1274
1899
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1900
|
+
appFormats: prevState?.appFormats,
|
|
1901
|
+
techStackId: prevState?.techStackId,
|
|
1275
1902
|
});
|
|
1276
|
-
logEvent(root, 'step', { step:
|
|
1277
|
-
stepHeader(
|
|
1903
|
+
logEvent(root, 'step', { step: 11, label: 'User Scenarios', feature });
|
|
1904
|
+
stepHeader(11, 'User Scenarios', feature);
|
|
1278
1905
|
if (isResuming) {
|
|
1279
|
-
printResumptionHeader(
|
|
1906
|
+
printResumptionHeader(11);
|
|
1280
1907
|
}
|
|
1281
|
-
console.log('Create per-persona variations of existing scenarios. Skip to step
|
|
1908
|
+
console.log('Create per-persona variations of existing scenarios. Skip to step 12 if no users.');
|
|
1282
1909
|
console.log();
|
|
1283
1910
|
console.log(chalk.bold('If the app has NO users/auth:'));
|
|
1284
|
-
console.log(chalk.dim(' Skip this step and proceed to step
|
|
1911
|
+
console.log(chalk.dim(' Skip this step and proceed to step 12 (Verify).'));
|
|
1285
1912
|
console.log();
|
|
1286
1913
|
console.log(chalk.bold('If the app has users/auth:'));
|
|
1287
1914
|
console.log();
|
|
1288
1915
|
console.log(chalk.bold('Goal: Create persona variations of EXISTING app scenarios.'));
|
|
1289
1916
|
console.log(chalk.dim(' Do NOT create standalone persona scenarios. Each user-persona scenario should be'));
|
|
1290
|
-
console.log(chalk.dim(' a variation of an existing app scenario from step
|
|
1917
|
+
console.log(chalk.dim(' a variation of an existing app scenario from step 10, with user-specific state layered on.'));
|
|
1291
1918
|
console.log();
|
|
1292
1919
|
console.log(chalk.bold('Checklist:'));
|
|
1293
1920
|
checkbox('Run `codeyam editor scenarios` — list all existing app scenarios');
|
|
1294
1921
|
checkbox('For EACH existing app scenario, create a logged-in variation:');
|
|
1295
1922
|
console.log(chalk.dim(' Copy the scenario seed data and add "session":{"cookieValue":"sess_alice"} + user seed'));
|
|
1296
1923
|
console.log(chalk.dim(' Name: "<Original Name> - Logged In" (e.g. "Full Catalog - Logged In")'));
|
|
1297
|
-
console.log(chalk.dim(' Step
|
|
1924
|
+
console.log(chalk.dim(' Step 10 scenarios already serve as logged-out versions (no session cookie)'));
|
|
1298
1925
|
checkbox('If there are multiple user roles (admin, regular, etc.), create role-specific variations too');
|
|
1299
1926
|
console.log(chalk.dim(' Each persona scenario layers user-specific seed data on top of an app scenario'));
|
|
1300
1927
|
checkbox('Include "dimensions" — inherit from the base app scenario, or override if the persona implies a different device');
|
|
@@ -1303,26 +1930,28 @@ function printStep9(root, feature) {
|
|
|
1303
1930
|
console.log(chalk.yellow(' If clientErrors is non-empty → fix the issue and re-register the scenario'));
|
|
1304
1931
|
console.log();
|
|
1305
1932
|
console.log(chalk.dim('See FEATURE_PATTERNS.md and AUTH_PATTERNS.md for auth scenario guidance.'));
|
|
1306
|
-
stopGate(
|
|
1933
|
+
stopGate(11);
|
|
1307
1934
|
}
|
|
1308
|
-
// ─── Step
|
|
1309
|
-
function
|
|
1935
|
+
// ─── Step 12: Verify ──────────────────────────────────────────────────
|
|
1936
|
+
function printStep12(root, feature) {
|
|
1310
1937
|
const port = getServerPort();
|
|
1311
1938
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1312
1939
|
const prevState = readState(root);
|
|
1313
|
-
const isResuming = prevState?.step ===
|
|
1940
|
+
const isResuming = prevState?.step === 12;
|
|
1314
1941
|
const now = new Date().toISOString();
|
|
1315
1942
|
writeState(root, {
|
|
1316
1943
|
feature,
|
|
1317
|
-
step:
|
|
1318
|
-
label: STEP_LABELS[
|
|
1944
|
+
step: 12,
|
|
1945
|
+
label: STEP_LABELS[12],
|
|
1319
1946
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1320
1947
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1948
|
+
appFormats: prevState?.appFormats,
|
|
1949
|
+
techStackId: prevState?.techStackId,
|
|
1321
1950
|
});
|
|
1322
|
-
logEvent(root, 'step', { step:
|
|
1323
|
-
stepHeader(
|
|
1951
|
+
logEvent(root, 'step', { step: 12, label: 'Verify', feature });
|
|
1952
|
+
stepHeader(12, 'Verify', feature);
|
|
1324
1953
|
if (isResuming) {
|
|
1325
|
-
printResumptionHeader(
|
|
1954
|
+
printResumptionHeader(12);
|
|
1326
1955
|
}
|
|
1327
1956
|
console.log('Verify component isolation screenshots, editor scenarios, and library tests.');
|
|
1328
1957
|
console.log();
|
|
@@ -1345,63 +1974,67 @@ function printStep10(root, feature) {
|
|
|
1345
1974
|
checkbox('Verify no broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1346
1975
|
console.log();
|
|
1347
1976
|
console.log(chalk.bold('Library functions — test check:'));
|
|
1348
|
-
checkbox('Re-run all test files from step
|
|
1977
|
+
checkbox('Re-run all test files from step 7 to confirm they still pass');
|
|
1349
1978
|
console.log(chalk.dim(' Example: npx vitest run app/lib/drinks.test.ts'));
|
|
1350
1979
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1351
1980
|
console.log();
|
|
1352
1981
|
console.log(chalk.dim('Focus on fixing issues. All component screenshots, scenarios, and tests must be clean before proceeding.'));
|
|
1353
|
-
stopGate(
|
|
1982
|
+
stopGate(12);
|
|
1354
1983
|
}
|
|
1355
|
-
// ─── Step
|
|
1356
|
-
function
|
|
1984
|
+
// ─── Step 13: Journal ─────────────────────────────────────────────────
|
|
1985
|
+
function printStep13(root, feature) {
|
|
1357
1986
|
const port = getServerPort();
|
|
1358
1987
|
const prevState = readState(root);
|
|
1359
|
-
const isResuming = prevState?.step ===
|
|
1988
|
+
const isResuming = prevState?.step === 13;
|
|
1360
1989
|
const now = new Date().toISOString();
|
|
1361
1990
|
writeState(root, {
|
|
1362
1991
|
feature,
|
|
1363
|
-
step:
|
|
1364
|
-
label: STEP_LABELS[
|
|
1992
|
+
step: 13,
|
|
1993
|
+
label: STEP_LABELS[13],
|
|
1365
1994
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1366
1995
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
1996
|
+
appFormats: prevState?.appFormats,
|
|
1997
|
+
techStackId: prevState?.techStackId,
|
|
1367
1998
|
});
|
|
1368
|
-
logEvent(root, 'step', { step:
|
|
1369
|
-
stepHeader(
|
|
1999
|
+
logEvent(root, 'step', { step: 13, label: 'Journal', feature });
|
|
2000
|
+
stepHeader(13, 'Journal', feature);
|
|
1370
2001
|
if (isResuming) {
|
|
1371
|
-
printResumptionHeader(
|
|
2002
|
+
printResumptionHeader(13);
|
|
1372
2003
|
}
|
|
1373
2004
|
console.log('Create or update the journal entry for this feature.');
|
|
1374
2005
|
console.log();
|
|
1375
2006
|
console.log(chalk.bold('Checklist:'));
|
|
1376
2007
|
checkbox('Write a concise description of what was built (2-3 sentences)');
|
|
1377
|
-
checkbox(`First time at step
|
|
2008
|
+
checkbox(`First time at step 13 — create journal entry with ALL session screenshots:`);
|
|
1378
2009
|
console.log(chalk.dim(` codeyam editor journal '{"title":"...","type":"feature","description":"...","includeSessionScenarios":true}'`));
|
|
1379
2010
|
console.log(chalk.dim(' includeSessionScenarios auto-discovers component + app + user persona screenshots'));
|
|
1380
|
-
checkbox(`Returning to step
|
|
2011
|
+
checkbox(`Returning to step 13 (before commit) — update the existing journal entry:`);
|
|
1381
2012
|
console.log(chalk.dim(` codeyam editor journal-update '{"time":"<journal entry time>","description":"<updated>","includeSessionScenarios":true}'`));
|
|
1382
2013
|
console.log(chalk.dim(' Note: PATCH only works before the entry is committed. After commit, use POST to create a new entry.'));
|
|
1383
2014
|
console.log();
|
|
1384
|
-
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step
|
|
1385
|
-
stopGate(
|
|
2015
|
+
console.log(chalk.dim('Focus on creating or updating the journal entry. Summary and presentation happen in step 15.'));
|
|
2016
|
+
stopGate(13);
|
|
1386
2017
|
}
|
|
1387
|
-
// ─── Step
|
|
1388
|
-
function
|
|
2018
|
+
// ─── Step 14: Review ──────────────────────────────────────────────────
|
|
2019
|
+
function printStep14(root, feature) {
|
|
1389
2020
|
const port = getServerPort();
|
|
1390
2021
|
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1391
2022
|
const prevState = readState(root);
|
|
1392
|
-
const isResuming = prevState?.step ===
|
|
2023
|
+
const isResuming = prevState?.step === 14;
|
|
1393
2024
|
const now = new Date().toISOString();
|
|
1394
2025
|
writeState(root, {
|
|
1395
2026
|
feature,
|
|
1396
|
-
step:
|
|
1397
|
-
label: STEP_LABELS[
|
|
2027
|
+
step: 14,
|
|
2028
|
+
label: STEP_LABELS[14],
|
|
1398
2029
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1399
2030
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2031
|
+
appFormats: prevState?.appFormats,
|
|
2032
|
+
techStackId: prevState?.techStackId,
|
|
1400
2033
|
});
|
|
1401
|
-
logEvent(root, 'step', { step:
|
|
1402
|
-
stepHeader(
|
|
2034
|
+
logEvent(root, 'step', { step: 14, label: 'Review', feature });
|
|
2035
|
+
stepHeader(14, 'Review', feature);
|
|
1403
2036
|
if (isResuming) {
|
|
1404
|
-
printResumptionHeader(
|
|
2037
|
+
printResumptionHeader(14);
|
|
1405
2038
|
}
|
|
1406
2039
|
console.log('Verify all screenshots and checks pass before presenting to the user.');
|
|
1407
2040
|
console.log();
|
|
@@ -1414,44 +2047,38 @@ function printStep12(root, feature) {
|
|
|
1414
2047
|
checkbox('If `hasErrors` is true, fix them and re-capture affected scenarios');
|
|
1415
2048
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1"]}\'` with all page paths and image URLs');
|
|
1416
2049
|
checkbox('Fix or remove any broken images before continuing');
|
|
2050
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
1417
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": {...}}'`));
|
|
1418
2054
|
checkbox('Do not proceed until all checks pass');
|
|
1419
|
-
stopGate(
|
|
2055
|
+
stopGate(14);
|
|
1420
2056
|
}
|
|
1421
|
-
// ─── Step
|
|
1422
|
-
function
|
|
1423
|
-
const port = getServerPort();
|
|
1424
|
-
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
2057
|
+
// ─── Step 15: Present ─────────────────────────────────────────────────
|
|
2058
|
+
function printStep15(root, feature) {
|
|
1425
2059
|
const prevState = readState(root);
|
|
1426
|
-
const isResuming = prevState?.step ===
|
|
2060
|
+
const isResuming = prevState?.step === 15;
|
|
1427
2061
|
const now = new Date().toISOString();
|
|
1428
2062
|
writeState(root, {
|
|
1429
2063
|
feature,
|
|
1430
|
-
step:
|
|
1431
|
-
label: STEP_LABELS[
|
|
2064
|
+
step: 15,
|
|
2065
|
+
label: STEP_LABELS[15],
|
|
1432
2066
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
1433
2067
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2068
|
+
appFormats: prevState?.appFormats,
|
|
2069
|
+
techStackId: prevState?.techStackId,
|
|
1434
2070
|
});
|
|
1435
|
-
logEvent(root, 'step', { step:
|
|
1436
|
-
stepHeader(
|
|
2071
|
+
logEvent(root, 'step', { step: 15, label: 'Present', feature });
|
|
2072
|
+
stepHeader(15, 'Present', feature);
|
|
1437
2073
|
if (isResuming) {
|
|
1438
|
-
printResumptionHeader(
|
|
2074
|
+
printResumptionHeader(15);
|
|
1439
2075
|
}
|
|
1440
2076
|
console.log('Present the results to the user and get their approval.');
|
|
1441
2077
|
console.log();
|
|
1442
2078
|
console.log(chalk.bold('Checklist:'));
|
|
1443
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1444
|
-
console.log(chalk.dim(' Each scenario has a `changeStatus` field: "new", "edited", "impacted", or null.'));
|
|
1445
|
-
checkbox('Pick the best scenario to showcase from this working session:');
|
|
1446
|
-
console.log(chalk.dim(' Prefer scenarios with changeStatus "new" or "edited" (directly changed in this session).'));
|
|
1447
|
-
console.log(chalk.dim(' For app-level scenarios, prefer those showing the new/changed functionality.'));
|
|
1448
|
-
console.log(chalk.dim(' NEVER pick a scenario with changeStatus null — those are unchanged from a previous commit.'));
|
|
1449
|
-
checkbox('Switch the preview to that scenario using its `id` and `dimension`:');
|
|
1450
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1451
|
-
console.log(chalk.dim(' Use the dimension that matches the scenario — check its registered dimensions.'));
|
|
1452
|
-
printDimensionGuidance(dim, dimNames);
|
|
1453
2079
|
checkbox(`Show the results panel: \`codeyam editor show-results\``);
|
|
1454
2080
|
console.log(chalk.dim(' This opens a visual panel below the terminal showing all scenarios with screenshots.'));
|
|
2081
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1455
2082
|
console.log(chalk.dim(' The user can click scenarios to switch the live preview.'));
|
|
1456
2083
|
checkbox('Write a 1-2 sentence summary of what was built');
|
|
1457
2084
|
checkbox('Report test count and audit status (one line)');
|
|
@@ -1463,7 +2090,7 @@ function printStep13(root, feature) {
|
|
|
1463
2090
|
chalk.dim(' — describe changes, then re-verify'));
|
|
1464
2091
|
console.log();
|
|
1465
2092
|
console.log(chalk.bold('If the user chooses "Save & commit":'));
|
|
1466
|
-
checkbox('Advance to the commit step: `codeyam editor
|
|
2093
|
+
checkbox('Advance to the commit step: `codeyam editor 16`');
|
|
1467
2094
|
console.log();
|
|
1468
2095
|
console.log(chalk.bold('If the user chooses "Make changes" (or asks for ANY change, even as a question):'));
|
|
1469
2096
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
@@ -1471,7 +2098,7 @@ function printStep13(root, feature) {
|
|
|
1471
2098
|
checkbox(`Run: \`codeyam editor change "${feature}"\` — this gives you the change checklist`);
|
|
1472
2099
|
checkbox('THEN make the requested changes and follow the checklist');
|
|
1473
2100
|
console.log(chalk.red.bold(' IMPORTANT: Always run the change command BEFORE writing any code.'));
|
|
1474
|
-
stopGate(
|
|
2101
|
+
stopGate(15, { confirm: true });
|
|
1475
2102
|
}
|
|
1476
2103
|
// ─── Migration Mode ──────────────────────────────────────────────────
|
|
1477
2104
|
/**
|
|
@@ -1668,7 +2295,7 @@ function printMigrateStep3(root) {
|
|
|
1668
2295
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1669
2296
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1670
2297
|
console.log();
|
|
1671
|
-
printComponentCaptureInstructions();
|
|
2298
|
+
printComponentCaptureInstructions(root);
|
|
1672
2299
|
console.log();
|
|
1673
2300
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1674
2301
|
}
|
|
@@ -1681,7 +2308,6 @@ function printMigrateStep4(root) {
|
|
|
1681
2308
|
}
|
|
1682
2309
|
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1683
2310
|
writeMigrationStepState(root, 4, pageName, pageIndex, totalPages);
|
|
1684
|
-
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1685
2311
|
console.log();
|
|
1686
2312
|
console.log(chalk.bold.cyan(`━━━ Migration Step 4: Preview — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1687
2313
|
console.log();
|
|
@@ -1691,11 +2317,8 @@ function printMigrateStep4(root) {
|
|
|
1691
2317
|
checkbox('Review all scenario screenshots for this page — do they look correct?');
|
|
1692
2318
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
1693
2319
|
checkbox('If any issues: fix the scenario data, re-register affected scenarios');
|
|
1694
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1695
|
-
checkbox('Pick the best scenario to showcase:');
|
|
1696
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1697
|
-
printDimensionGuidance(dim, dimNames);
|
|
1698
2320
|
checkbox('Show results: `codeyam editor show-results`');
|
|
2321
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1699
2322
|
console.log();
|
|
1700
2323
|
console.log(chalk.dim('The user should now see their existing page with live screenshots in the preview.'));
|
|
1701
2324
|
migrationStopGate(4, pageName, pageIndex, totalPages);
|
|
@@ -1807,7 +2430,7 @@ function printMigrateStep8(root) {
|
|
|
1807
2430
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1808
2431
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1809
2432
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1810
|
-
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)');
|
|
1811
2434
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1812
2435
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1813
2436
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1815,10 +2438,10 @@ function printMigrateStep8(root) {
|
|
|
1815
2438
|
console.log();
|
|
1816
2439
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1817
2440
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1818
|
-
printComponentCaptureInstructions();
|
|
2441
|
+
printComponentCaptureInstructions(root);
|
|
1819
2442
|
console.log();
|
|
1820
2443
|
console.log(chalk.bold('Verify:'));
|
|
1821
|
-
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)');
|
|
1822
2445
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1823
2446
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1824
2447
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -1855,18 +2478,14 @@ function printMigrateStep10(root) {
|
|
|
1855
2478
|
}
|
|
1856
2479
|
const { pageName, pageIndex, totalPages } = pageInfo;
|
|
1857
2480
|
writeMigrationStepState(root, 10, pageName, pageIndex, totalPages);
|
|
1858
|
-
const { defaultName: dim, names: dimNames } = getProjectDimensions(root);
|
|
1859
2481
|
console.log();
|
|
1860
2482
|
console.log(chalk.bold.cyan(`━━━ Migration Step 10: Present — ${pageName} (${pageIndex + 1}/${totalPages}) ━━━`));
|
|
1861
2483
|
console.log();
|
|
1862
2484
|
console.log("Show results and commit this page's migration.");
|
|
1863
2485
|
console.log();
|
|
1864
2486
|
console.log(chalk.bold('Checklist:'));
|
|
1865
|
-
checkbox('Fetch all scenarios: `codeyam editor scenarios`');
|
|
1866
|
-
checkbox('Pick the best scenario to showcase:');
|
|
1867
|
-
console.log(chalk.dim(` codeyam editor preview '{"scenarioId":"<id>","dimension":"${dim}"}'`));
|
|
1868
|
-
printDimensionGuidance(dim, dimNames);
|
|
1869
2487
|
checkbox('Show results: `codeyam editor show-results`');
|
|
2488
|
+
console.log(chalk.dim(' The first scenario is automatically selected and its live preview is shown.'));
|
|
1870
2489
|
checkbox('Write a 1-2 sentence summary of what was migrated');
|
|
1871
2490
|
console.log();
|
|
1872
2491
|
console.log(chalk.bold('Present a selection menu (AskUserQuestion with these EXACT option labels):'));
|
|
@@ -2086,47 +2705,52 @@ function handleMigrateCommand(root, subArg) {
|
|
|
2086
2705
|
};
|
|
2087
2706
|
stepFns[step](root);
|
|
2088
2707
|
}
|
|
2089
|
-
// ─── Step
|
|
2090
|
-
function
|
|
2708
|
+
// ─── Step 16: Commit ─────────────────────────────────────────────────
|
|
2709
|
+
function printStep16(root, feature) {
|
|
2091
2710
|
const prevState = readState(root);
|
|
2092
|
-
const isResuming = prevState?.step ===
|
|
2711
|
+
const isResuming = prevState?.step === 16;
|
|
2093
2712
|
const now = new Date().toISOString();
|
|
2094
2713
|
writeState(root, {
|
|
2095
2714
|
feature,
|
|
2096
|
-
step:
|
|
2097
|
-
label: STEP_LABELS[
|
|
2715
|
+
step: 16,
|
|
2716
|
+
label: STEP_LABELS[16],
|
|
2098
2717
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2099
2718
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2719
|
+
appFormats: prevState?.appFormats,
|
|
2720
|
+
techStackId: prevState?.techStackId,
|
|
2100
2721
|
});
|
|
2101
|
-
logEvent(root, 'step', { step:
|
|
2102
|
-
stepHeader(
|
|
2722
|
+
logEvent(root, 'step', { step: 16, label: 'Commit', feature });
|
|
2723
|
+
stepHeader(16, 'Commit', feature);
|
|
2103
2724
|
if (isResuming) {
|
|
2104
|
-
printResumptionHeader(
|
|
2725
|
+
printResumptionHeader(16);
|
|
2105
2726
|
}
|
|
2106
2727
|
console.log('Commit all changes for this feature.');
|
|
2107
2728
|
console.log();
|
|
2108
2729
|
console.log(chalk.bold('Checklist:'));
|
|
2730
|
+
checkbox('Ensure all screenshots are fresh: `codeyam editor recapture-stale`');
|
|
2109
2731
|
checkbox(`Hide the results panel: \`codeyam editor hide-results\``);
|
|
2110
2732
|
checkbox(`Git commit using the journal description: \`codeyam editor commit '{"message":"feat: <title>\\n\\n<journal description>"}'\``);
|
|
2111
2733
|
console.log(chalk.dim(' The commit message body MUST match the journal description exactly'));
|
|
2112
|
-
stopGate(
|
|
2734
|
+
stopGate(16);
|
|
2113
2735
|
}
|
|
2114
|
-
// ─── Step
|
|
2115
|
-
function
|
|
2736
|
+
// ─── Step 17: Finalize ───────────────────────────────────────────────
|
|
2737
|
+
function printStep17(root, feature) {
|
|
2116
2738
|
const prevState = readState(root);
|
|
2117
|
-
const isResuming = prevState?.step ===
|
|
2739
|
+
const isResuming = prevState?.step === 17;
|
|
2118
2740
|
const now = new Date().toISOString();
|
|
2119
2741
|
writeState(root, {
|
|
2120
2742
|
feature,
|
|
2121
|
-
step:
|
|
2122
|
-
label: STEP_LABELS[
|
|
2743
|
+
step: 17,
|
|
2744
|
+
label: STEP_LABELS[17],
|
|
2123
2745
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2124
2746
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2747
|
+
appFormats: prevState?.appFormats,
|
|
2748
|
+
techStackId: prevState?.techStackId,
|
|
2125
2749
|
});
|
|
2126
|
-
logEvent(root, 'step', { step:
|
|
2127
|
-
stepHeader(
|
|
2750
|
+
logEvent(root, 'step', { step: 17, label: 'Finalize', feature });
|
|
2751
|
+
stepHeader(17, 'Finalize', feature);
|
|
2128
2752
|
if (isResuming) {
|
|
2129
|
-
printResumptionHeader(
|
|
2753
|
+
printResumptionHeader(17);
|
|
2130
2754
|
}
|
|
2131
2755
|
console.log('Update the journal with the commit SHA and amend the commit.');
|
|
2132
2756
|
console.log();
|
|
@@ -2134,24 +2758,26 @@ function printStep15(root, feature) {
|
|
|
2134
2758
|
checkbox(`Update journal with commit SHA: \`codeyam editor journal-update '{"time":"<journal entry time>","commitSha":"<sha>","commitMessage":"feat: <title>"}'\``);
|
|
2135
2759
|
checkbox('Amend the commit to include the journal update: `git add .codeyam/journal/ && git commit --amend --no-edit`');
|
|
2136
2760
|
console.log(chalk.dim(' The journal-update modifies journal files after the commit — amend to keep the tree clean.'));
|
|
2137
|
-
stopGate(
|
|
2761
|
+
stopGate(17);
|
|
2138
2762
|
}
|
|
2139
|
-
// ─── Step
|
|
2140
|
-
function
|
|
2763
|
+
// ─── Step 18: Push ───────────────────────────────────────────────────
|
|
2764
|
+
function printStep18(root, feature) {
|
|
2141
2765
|
const prevState = readState(root);
|
|
2142
|
-
const isResuming = prevState?.step ===
|
|
2766
|
+
const isResuming = prevState?.step === 18;
|
|
2143
2767
|
const now = new Date().toISOString();
|
|
2144
2768
|
writeState(root, {
|
|
2145
2769
|
feature,
|
|
2146
|
-
step:
|
|
2147
|
-
label: STEP_LABELS[
|
|
2770
|
+
step: 18,
|
|
2771
|
+
label: STEP_LABELS[18],
|
|
2148
2772
|
startedAt: isResuming ? prevState.startedAt : now,
|
|
2149
2773
|
featureStartedAt: prevState?.featureStartedAt || now,
|
|
2774
|
+
appFormats: prevState?.appFormats,
|
|
2775
|
+
techStackId: prevState?.techStackId,
|
|
2150
2776
|
});
|
|
2151
|
-
logEvent(root, 'step', { step:
|
|
2152
|
-
stepHeader(
|
|
2777
|
+
logEvent(root, 'step', { step: 18, label: 'Push', feature });
|
|
2778
|
+
stepHeader(18, 'Push', feature);
|
|
2153
2779
|
if (isResuming) {
|
|
2154
|
-
printResumptionHeader(
|
|
2780
|
+
printResumptionHeader(18);
|
|
2155
2781
|
}
|
|
2156
2782
|
console.log('Push the commit to the remote repository.');
|
|
2157
2783
|
console.log();
|
|
@@ -2162,12 +2788,15 @@ function printStep16(root, feature) {
|
|
|
2162
2788
|
console.log(chalk.dim(' If the user says yes, run: `git push`'));
|
|
2163
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"'));
|
|
2164
2790
|
console.log(chalk.dim(' If the user wants help, guide them through `gh repo create` or manual GitHub setup, then push.'));
|
|
2165
|
-
|
|
2791
|
+
console.log(chalk.dim(' Task management is handled by the ━━━ TASK ━━━ directive below.'));
|
|
2166
2792
|
console.log();
|
|
2167
|
-
|
|
2168
|
-
console.log(chalk.
|
|
2169
|
-
console.log(chalk.
|
|
2170
|
-
|
|
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.'));
|
|
2797
|
+
console.log(chalk.yellow(' Do NOT use `codeyam editor change` or start building directly.'));
|
|
2798
|
+
console.log(chalk.yellow(' The change workflow is ONLY for modifications during steps 1-15, not after commit.'));
|
|
2799
|
+
stopGate(18, { confirm: true });
|
|
2171
2800
|
}
|
|
2172
2801
|
// ─── Command definition ───────────────────────────────────────────────
|
|
2173
2802
|
// ─── Analyze-imports subcommand ────────────────────────────────────────
|
|
@@ -2187,7 +2816,7 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2187
2816
|
return;
|
|
2188
2817
|
}
|
|
2189
2818
|
console.error(chalk.red('Error: .codeyam/glossary.json not found.'));
|
|
2190
|
-
console.error(chalk.dim(' Run codeyam editor
|
|
2819
|
+
console.error(chalk.dim(' Run codeyam editor 8 to create the glossary first.'));
|
|
2191
2820
|
process.exit(1);
|
|
2192
2821
|
}
|
|
2193
2822
|
let glossaryEntries;
|
|
@@ -2196,10 +2825,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2196
2825
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2197
2826
|
}
|
|
2198
2827
|
catch {
|
|
2828
|
+
if (options.silent)
|
|
2829
|
+
return;
|
|
2199
2830
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2200
2831
|
process.exit(1);
|
|
2201
2832
|
}
|
|
2202
2833
|
if (glossaryEntries.length === 0) {
|
|
2834
|
+
if (options.silent)
|
|
2835
|
+
return;
|
|
2203
2836
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2204
2837
|
process.exit(1);
|
|
2205
2838
|
}
|
|
@@ -2241,25 +2874,137 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2241
2874
|
// Non-fatal — scenario file paths just won't be included
|
|
2242
2875
|
}
|
|
2243
2876
|
const progress = new ProgressReporter();
|
|
2244
|
-
//
|
|
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.
|
|
2245
2914
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2246
2915
|
// The analyzer will discover and create them from file paths.
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
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');
|
|
2255
2937
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
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
|
+
}
|
|
2261
3007
|
}
|
|
2262
|
-
progress.succeed('Analysis complete');
|
|
2263
3008
|
// Load entities WITH metadata from database
|
|
2264
3009
|
progress.start('Loading entity metadata...');
|
|
2265
3010
|
await initializeEnvironment();
|
|
@@ -2339,6 +3084,8 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2339
3084
|
sha: e.sha,
|
|
2340
3085
|
name: e.name,
|
|
2341
3086
|
filePath: e.filePath || '',
|
|
3087
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3088
|
+
e.metadata?.namedExport === false,
|
|
2342
3089
|
})));
|
|
2343
3090
|
if (backfillResult.updated > 0 && !options.silent) {
|
|
2344
3091
|
console.log(chalk.green(`Linked ${backfillResult.updated} scenario(s) to their entities.`));
|
|
@@ -2440,43 +3187,98 @@ async function handleDelete(scenarioId) {
|
|
|
2440
3187
|
* Creates isolation route directories and the layout guard file.
|
|
2441
3188
|
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2442
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
|
+
}
|
|
2443
3208
|
function handleIsolate(componentNames) {
|
|
2444
3209
|
const root = process.cwd();
|
|
3210
|
+
const ctx = getTechStackContext(root);
|
|
2445
3211
|
if (componentNames.length === 0) {
|
|
2446
3212
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2447
3213
|
process.exit(1);
|
|
2448
3214
|
}
|
|
2449
3215
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2450
|
-
// Create
|
|
2451
|
-
|
|
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
|
+
}
|
|
2452
3223
|
if (!fs.existsSync(layoutPath)) {
|
|
2453
3224
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
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).
|
|
2466
3251
|
const created = [];
|
|
2467
3252
|
const existed = [];
|
|
2468
3253
|
for (const name of componentNames) {
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
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
|
+
}
|
|
2472
3262
|
}
|
|
2473
3263
|
else {
|
|
2474
|
-
|
|
2475
|
-
|
|
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
|
+
}
|
|
2476
3272
|
}
|
|
2477
3273
|
}
|
|
2478
3274
|
if (created.length > 0) {
|
|
2479
|
-
|
|
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
|
+
}
|
|
2480
3282
|
}
|
|
2481
3283
|
if (existed.length > 0) {
|
|
2482
3284
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2530,6 +3332,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2530
3332
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2531
3333
|
if (data.sessionsNotified != null)
|
|
2532
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
|
+
}
|
|
2533
3341
|
return parts.join(' ');
|
|
2534
3342
|
}
|
|
2535
3343
|
case 'dev-server': {
|
|
@@ -2579,10 +3387,82 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2579
3387
|
}
|
|
2580
3388
|
return parts.join(' ');
|
|
2581
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
|
+
}
|
|
2582
3418
|
default:
|
|
2583
3419
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2584
3420
|
}
|
|
2585
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
|
+
}
|
|
2586
3466
|
/**
|
|
2587
3467
|
* `codeyam editor register '{"name":"...","componentName":"...",...}'`
|
|
2588
3468
|
*
|
|
@@ -2607,11 +3487,15 @@ async function handleRegister(jsonArg) {
|
|
|
2607
3487
|
}
|
|
2608
3488
|
// Normalize to array for uniform handling
|
|
2609
3489
|
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
3490
|
+
const root = getProjectRoot();
|
|
2610
3491
|
const port = getServerPort();
|
|
2611
3492
|
const url = `http://localhost:${port}/api/editor-register-scenario`;
|
|
2612
3493
|
const isBatch = items.length > 1;
|
|
2613
3494
|
let succeeded = 0;
|
|
2614
3495
|
let failed = 0;
|
|
3496
|
+
let warnings = 0;
|
|
3497
|
+
const issues = [];
|
|
3498
|
+
const screenshotEntries = [];
|
|
2615
3499
|
for (let i = 0; i < items.length; i++) {
|
|
2616
3500
|
const body = items[i];
|
|
2617
3501
|
const prefix = isBatch ? chalk.dim(`[${i + 1}/${items.length}] `) : '';
|
|
@@ -2639,11 +3523,30 @@ async function handleRegister(jsonArg) {
|
|
|
2639
3523
|
else {
|
|
2640
3524
|
parts.push(`errors=0`);
|
|
2641
3525
|
}
|
|
2642
|
-
if (data.
|
|
2643
|
-
|
|
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
|
+
}
|
|
3532
|
+
if (data.seedResult) {
|
|
3533
|
+
if (data.seedResult.success) {
|
|
3534
|
+
parts.push(`seed=ok`);
|
|
3535
|
+
}
|
|
3536
|
+
else {
|
|
3537
|
+
parts.push(chalk.red(`seed=FAILED${data.seedResult.error ? ` (${data.seedResult.error})` : ''}`));
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
2644
3540
|
if (data.captureError)
|
|
2645
3541
|
parts.push(chalk.yellow(`captureError="${data.captureError}"`));
|
|
2646
3542
|
console.log(prefix + parts.join(' '));
|
|
3543
|
+
// Warn when application scenario is missing seed data
|
|
3544
|
+
if (data.missingSeedWarning) {
|
|
3545
|
+
console.log(chalk.red.bold('WARNING: No seed data in this application scenario!'));
|
|
3546
|
+
console.log(chalk.yellow(' This scenario has type "application" but no "seed" data was provided.'));
|
|
3547
|
+
console.log(chalk.yellow(' The page will render with an empty database — nothing will be visible.'));
|
|
3548
|
+
console.log(chalk.yellow(' Re-register with "seed":{...} containing the database rows this page needs.'));
|
|
3549
|
+
}
|
|
2647
3550
|
// Surface client errors prominently so they can't be missed
|
|
2648
3551
|
if (data.clientErrors && data.clientErrors.length > 0) {
|
|
2649
3552
|
console.log(chalk.red.bold(`⚠ WARNING: ${data.clientErrors.length} client error${data.clientErrors.length !== 1 ? 's' : ''} detected during capture:`));
|
|
@@ -2651,14 +3554,39 @@ async function handleRegister(jsonArg) {
|
|
|
2651
3554
|
console.log(chalk.red(` → ${err}`));
|
|
2652
3555
|
}
|
|
2653
3556
|
console.log(chalk.yellow(' The screenshot may show an error screen instead of the actual component.'));
|
|
3557
|
+
// Provide actionable debugging hints for HTTP/API errors
|
|
3558
|
+
const hasApiResponseError = data.clientErrors.some((e) => e.includes('API response error:'));
|
|
3559
|
+
const hasHttpError = data.clientErrors.some((e) => e.includes('HTTP error:'));
|
|
3560
|
+
if (hasHttpError || hasApiResponseError) {
|
|
3561
|
+
console.log(chalk.yellow(' To debug: look at the "API response error" lines above — they show the exact API endpoint'));
|
|
3562
|
+
console.log(chalk.yellow(' that failed and what the server returned. Then check the scenario seed data to ensure'));
|
|
3563
|
+
console.log(chalk.yellow(' all IDs referenced in the URL exist in the seed, and that the route is correct.'));
|
|
3564
|
+
}
|
|
2654
3565
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2655
3566
|
}
|
|
3567
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2656
3568
|
if (!res.ok) {
|
|
2657
3569
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2658
3570
|
failed++;
|
|
2659
3571
|
}
|
|
2660
3572
|
else {
|
|
3573
|
+
if (resultIssues.length > 0)
|
|
3574
|
+
warnings++;
|
|
2661
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}`);
|
|
2662
3590
|
}
|
|
2663
3591
|
}
|
|
2664
3592
|
catch (error) {
|
|
@@ -2669,12 +3597,99 @@ async function handleRegister(jsonArg) {
|
|
|
2669
3597
|
failed++;
|
|
2670
3598
|
}
|
|
2671
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
|
+
}
|
|
2672
3627
|
if (isBatch) {
|
|
2673
|
-
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
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
if (failed > 0 || warnings > 0) {
|
|
3644
|
+
process.exit(1);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
// ─── Glossary-add subcommand ──────────────────────────────────────────
|
|
3648
|
+
/**
|
|
3649
|
+
* `codeyam editor glossary-add '{"name":"...", "filePath":"...", "description":"..."}'`
|
|
3650
|
+
*
|
|
3651
|
+
* Safely adds/updates entries in .codeyam/glossary.json via the CLI,
|
|
3652
|
+
* avoiding hand-editing that breaks on unicode characters.
|
|
3653
|
+
*/
|
|
3654
|
+
async function handleGlossaryAdd(jsonArg) {
|
|
3655
|
+
if (!jsonArg) {
|
|
3656
|
+
console.error(chalk.red('Error: JSON argument required.'));
|
|
3657
|
+
console.error(chalk.dim(' Usage: codeyam editor glossary-add \'{"name":"DrinkCard","filePath":"app/components/DrinkCard.tsx","description":"Displays a drink item"}\''));
|
|
3658
|
+
console.error(chalk.dim(' For large payloads: codeyam editor glossary-add @.codeyam/tmp/entry.json'));
|
|
3659
|
+
process.exit(1);
|
|
3660
|
+
}
|
|
3661
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
3662
|
+
if (parsed.error) {
|
|
3663
|
+
console.error(chalk.red(`Error: ${parsed.error}`));
|
|
3664
|
+
process.exit(1);
|
|
3665
|
+
}
|
|
3666
|
+
// Normalize to array
|
|
3667
|
+
const items = Array.isArray(parsed.body) ? parsed.body : [parsed.body];
|
|
3668
|
+
// Read existing glossary
|
|
3669
|
+
const root = getProjectRoot();
|
|
3670
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
3671
|
+
let existing = [];
|
|
3672
|
+
try {
|
|
3673
|
+
const raw = JSON.parse(fs.readFileSync(glossaryPath, 'utf8'));
|
|
3674
|
+
existing = sanitizeGlossaryEntries(raw);
|
|
2674
3675
|
}
|
|
2675
|
-
|
|
3676
|
+
catch {
|
|
3677
|
+
// Glossary doesn't exist yet or can't be parsed — start fresh
|
|
3678
|
+
}
|
|
3679
|
+
// Merge
|
|
3680
|
+
const { validateGlossaryEntry, mergeGlossaryEntries } = await import('../utils/glossaryAdd.js');
|
|
3681
|
+
const result = mergeGlossaryEntries(existing, items);
|
|
3682
|
+
// Report validation errors
|
|
3683
|
+
for (const err of result.errors) {
|
|
3684
|
+
console.error(chalk.red(`Error at index ${err.index}: ${err.message}`));
|
|
3685
|
+
}
|
|
3686
|
+
if (result.added === 0 && result.updated === 0 && result.errors.length > 0) {
|
|
2676
3687
|
process.exit(1);
|
|
2677
3688
|
}
|
|
3689
|
+
// Write back with utf8 encoding to safely handle unicode
|
|
3690
|
+
fs.mkdirSync(path.dirname(glossaryPath), { recursive: true });
|
|
3691
|
+
fs.writeFileSync(glossaryPath, JSON.stringify(result.entries, null, 2), 'utf8');
|
|
3692
|
+
console.log(`added=${result.added} updated=${result.updated} total=${result.entries.length}`);
|
|
2678
3693
|
}
|
|
2679
3694
|
// ─── Dependents subcommand ────────────────────────────────────────────
|
|
2680
3695
|
/**
|
|
@@ -2797,7 +3812,7 @@ async function handleDependents(entityName) {
|
|
|
2797
3812
|
*
|
|
2798
3813
|
* Prints a condensed post-change checklist that guides Claude through
|
|
2799
3814
|
* re-verifying after user-requested modifications. When called from
|
|
2800
|
-
* step
|
|
3815
|
+
* step 15+, this loops back to step 15 (present). When called from an
|
|
2801
3816
|
* earlier step, it returns to that step so the normal flow continues.
|
|
2802
3817
|
*/
|
|
2803
3818
|
function handleChange(feature) {
|
|
@@ -2815,7 +3830,7 @@ function handleChange(feature) {
|
|
|
2815
3830
|
process.exit(1);
|
|
2816
3831
|
}
|
|
2817
3832
|
}
|
|
2818
|
-
const currentStep = state?.step ??
|
|
3833
|
+
const currentStep = state?.step ?? 15;
|
|
2819
3834
|
const port = getServerPort();
|
|
2820
3835
|
console.log();
|
|
2821
3836
|
console.log(chalk.bold.cyan('━━━ Change Loop ━━━'));
|
|
@@ -2837,6 +3852,8 @@ function handleChange(feature) {
|
|
|
2837
3852
|
console.log(chalk.cyan(` codeyam editor preview '{"dimension":"${dim}"}'`));
|
|
2838
3853
|
printDimensionGuidance(dim, dimNames);
|
|
2839
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.'));
|
|
2840
3857
|
console.log();
|
|
2841
3858
|
console.log(chalk.bold('0. Close the results panel:'));
|
|
2842
3859
|
checkbox(`Hide results: \`codeyam editor hide-results\``);
|
|
@@ -2863,6 +3880,7 @@ function handleChange(feature) {
|
|
|
2863
3880
|
console.log(chalk.dim(' re-register "Home - Default", "Catalog - Full", "Detail - WithReviews", etc.'));
|
|
2864
3881
|
checkbox("Enrich existing scenario data to exercise the change — don't just re-register unchanged data");
|
|
2865
3882
|
console.log(chalk.dim(' Add data that demonstrates what changed: new fields, relationships, states, content variety.'));
|
|
3883
|
+
console.log(chalk.dim(' If the enriched data makes the scenario name too narrow, rename it to reflect its broader coverage.'));
|
|
2866
3884
|
checkbox('After each re-registration, view the screenshot to verify data is visible');
|
|
2867
3885
|
console.log(chalk.dim(" If the screenshot doesn't show the data you put in, the scenario is broken."));
|
|
2868
3886
|
console.log();
|
|
@@ -2871,6 +3889,7 @@ function handleChange(feature) {
|
|
|
2871
3889
|
checkbox(`Navigate to key pages to verify changes: \`codeyam editor preview '{"path":"/your-route","dimension":"${dim}"}'\``);
|
|
2872
3890
|
printDimensionGuidance(dim, dimNames);
|
|
2873
3891
|
checkbox('Run `codeyam editor scenario-coverage` — all affected scenarios must be fresh');
|
|
3892
|
+
checkbox('Recapture stale scenarios: `codeyam editor recapture-stale`');
|
|
2874
3893
|
checkbox('Run `codeyam editor audit` — all checks must pass');
|
|
2875
3894
|
checkbox(`Check for client-side errors: \`codeyam editor client-errors\``);
|
|
2876
3895
|
console.log(chalk.dim(' If `hasContent=false`, the preview is blank — fix the code before proceeding.'));
|
|
@@ -2885,10 +3904,10 @@ function handleChange(feature) {
|
|
|
2885
3904
|
console.log(chalk.dim(' Always update the existing uncommitted entry — do NOT create a new one.'));
|
|
2886
3905
|
console.log(chalk.dim(' Only create a new entry (POST) if no uncommitted entry exists for this feature.'));
|
|
2887
3906
|
console.log();
|
|
2888
|
-
// If the change was initiated from a step before
|
|
2889
|
-
// instead of jumping to step
|
|
2890
|
-
// step
|
|
2891
|
-
if (currentStep <
|
|
3907
|
+
// If the change was initiated from a step before 15, return to that step
|
|
3908
|
+
// instead of jumping to step 15. The change workflow should only loop to
|
|
3909
|
+
// step 15 when changes are requested FROM step 15.
|
|
3910
|
+
if (currentStep < 15) {
|
|
2892
3911
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2893
3912
|
console.log(chalk.red.bold(' REQUIRED: Return to current step'));
|
|
2894
3913
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
@@ -2899,7 +3918,7 @@ function handleChange(feature) {
|
|
|
2899
3918
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2900
3919
|
console.log(chalk.red.bold(' REQUIRED: Show Results'));
|
|
2901
3920
|
console.log(chalk.red.bold(' When all checks pass, you MUST run: ') +
|
|
2902
|
-
chalk.bold(`codeyam editor
|
|
3921
|
+
chalk.bold(`codeyam editor 15`));
|
|
2903
3922
|
console.log(chalk.red.bold(' The user ALWAYS expects to see results after changes. DO NOT skip this step.'));
|
|
2904
3923
|
console.log(chalk.red(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2905
3924
|
}
|
|
@@ -2907,23 +3926,252 @@ function handleChange(feature) {
|
|
|
2907
3926
|
}
|
|
2908
3927
|
// ─── Audit gate ─────────────────────────────────────────────────────
|
|
2909
3928
|
/**
|
|
2910
|
-
*
|
|
2911
|
-
*
|
|
2912
|
-
* Used as a hard gate before steps 8+.
|
|
3929
|
+
* Fetch the audit result from the server.
|
|
3930
|
+
* Returns null if the server is unreachable.
|
|
2913
3931
|
*/
|
|
2914
|
-
async function
|
|
3932
|
+
async function fetchAuditResult(options) {
|
|
2915
3933
|
const port = getServerPort();
|
|
3934
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
2916
3935
|
try {
|
|
2917
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3936
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
2918
3937
|
if (!res.ok)
|
|
2919
|
-
return
|
|
2920
|
-
|
|
2921
|
-
return data?.summary?.allPassing === true;
|
|
3938
|
+
return null;
|
|
3939
|
+
return await res.json();
|
|
2922
3940
|
}
|
|
2923
3941
|
catch {
|
|
2924
|
-
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
/**
|
|
3946
|
+
* Silently check whether the audit passes. Returns true if allPassing,
|
|
3947
|
+
* false if any issues remain or the server is unreachable.
|
|
3948
|
+
* Used as a hard gate before steps 8+.
|
|
3949
|
+
*
|
|
3950
|
+
* Auto-runs analyze-imports if the only failure is incomplete entities,
|
|
3951
|
+
* then re-checks once.
|
|
3952
|
+
*/
|
|
3953
|
+
async function checkAuditGate() {
|
|
3954
|
+
const data = await fetchAuditResult();
|
|
3955
|
+
if (!data)
|
|
3956
|
+
return true; // Server unreachable — don't block
|
|
3957
|
+
if (data.summary?.allPassing === true)
|
|
3958
|
+
return true;
|
|
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)) {
|
|
2925
3965
|
return true;
|
|
2926
3966
|
}
|
|
3967
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
3968
|
+
try {
|
|
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
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
catch {
|
|
3983
|
+
// Fall through
|
|
3984
|
+
}
|
|
3985
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3986
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3987
|
+
if (retry?.summary?.allPassing === true)
|
|
3988
|
+
return true;
|
|
3989
|
+
}
|
|
3990
|
+
// Print specific failures so Claude knows what to fix without running audit separately
|
|
3991
|
+
printAuditGateFailures(data);
|
|
3992
|
+
return false;
|
|
3993
|
+
}
|
|
3994
|
+
/**
|
|
3995
|
+
* Print a concise summary of audit failures for the gate block message.
|
|
3996
|
+
* This gives Claude immediate context about what to fix without needing
|
|
3997
|
+
* to run `codeyam editor audit` as a separate step.
|
|
3998
|
+
*/
|
|
3999
|
+
function printAuditGateFailures(data) {
|
|
4000
|
+
const s = data.summary;
|
|
4001
|
+
if (!s)
|
|
4002
|
+
return;
|
|
4003
|
+
const issues = [];
|
|
4004
|
+
if (s.componentsMissing > 0) {
|
|
4005
|
+
issues.push(`${s.componentsMissing} component(s) missing scenarios`);
|
|
4006
|
+
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
4007
|
+
for (const c of missing) {
|
|
4008
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
4009
|
+
if (c.hint)
|
|
4010
|
+
issues.push(` Fix: ${c.hint}`);
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
if (s.componentsWithErrors > 0) {
|
|
4014
|
+
issues.push(`${s.componentsWithErrors} component(s) with client errors (browser API or runtime errors in captured scenarios)`);
|
|
4015
|
+
const withErrors = (data.components || []).filter((c) => c.status === 'has_errors');
|
|
4016
|
+
for (const c of withErrors) {
|
|
4017
|
+
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
if (s.functionsMissing > 0) {
|
|
4021
|
+
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
4022
|
+
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
4023
|
+
for (const f of missing) {
|
|
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
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
if (s.functionsFailing > 0) {
|
|
4036
|
+
issues.push(`${s.functionsFailing} function(s) with failing tests`);
|
|
4037
|
+
const failing = (data.functions || []).filter((f) => f.status === 'failing');
|
|
4038
|
+
for (const f of failing) {
|
|
4039
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
if (s.functionsRunnerError > 0) {
|
|
4043
|
+
issues.push(`${s.functionsRunnerError} function(s) with test runner errors (the runner crashed — not a test failure)`);
|
|
4044
|
+
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
4045
|
+
for (const f of runnerErrors) {
|
|
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}`);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (s.functionsNameMismatch > 0) {
|
|
4054
|
+
issues.push(`${s.functionsNameMismatch} function(s) with test name mismatch`);
|
|
4055
|
+
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
4056
|
+
for (const f of mismatch) {
|
|
4057
|
+
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
4058
|
+
if (f.hint)
|
|
4059
|
+
issues.push(` Fix: ${f.hint}`);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
if (s.missingFromGlossary > 0) {
|
|
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
|
+
}
|
|
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
|
+
}
|
|
4090
|
+
const preCount = s.preExistingIncompleteEntities || 0;
|
|
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) {
|
|
4096
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
4097
|
+
}
|
|
4098
|
+
else if (preCount > 0) {
|
|
4099
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis (${preCount} pre-existing — not from your changes)`);
|
|
4100
|
+
}
|
|
4101
|
+
else {
|
|
4102
|
+
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
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
|
+
}
|
|
4118
|
+
}
|
|
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) {
|
|
4135
|
+
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
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) {
|
|
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
|
+
}
|
|
4150
|
+
if (issues.length > 0) {
|
|
4151
|
+
console.error(chalk.yellow('\nAudit failures:'));
|
|
4152
|
+
for (const issue of issues) {
|
|
4153
|
+
console.error(chalk.yellow(` • ${issue}`));
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
// Surface client error details inline — these are the most common cause of
|
|
4157
|
+
// Claude looping because the errors require code fixes, not audit re-runs.
|
|
4158
|
+
if (data.components) {
|
|
4159
|
+
const withErrors = data.components.filter((c) => c.status === 'has_errors' && c.clientErrors?.length > 0);
|
|
4160
|
+
if (withErrors.length > 0) {
|
|
4161
|
+
console.error(chalk.yellow('\nClient errors found:'));
|
|
4162
|
+
for (const c of withErrors) {
|
|
4163
|
+
console.error(chalk.red(` ${c.name}:`));
|
|
4164
|
+
for (const err of c.clientErrors.slice(0, 3)) {
|
|
4165
|
+
console.error(chalk.red(` → ${err}`));
|
|
4166
|
+
}
|
|
4167
|
+
if (c.clientErrors.length > 3) {
|
|
4168
|
+
console.error(chalk.dim(` … and ${c.clientErrors.length - 3} more`));
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
console.error(chalk.yellow('\nFix: Fix the code errors above, then re-capture the affected scenarios.'));
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
console.error(chalk.dim('\nRun `codeyam editor audit` for full details.\n'));
|
|
2927
4175
|
}
|
|
2928
4176
|
// ─── Audit subcommand ────────────────────────────────────────────────
|
|
2929
4177
|
/**
|
|
@@ -2933,43 +4181,137 @@ async function checkAuditGate() {
|
|
|
2933
4181
|
* which glossary components have registered scenarios and which functions
|
|
2934
4182
|
* have test files. Exits with code 1 if anything is missing.
|
|
2935
4183
|
*/
|
|
2936
|
-
async function handleAudit() {
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
let data;
|
|
2940
|
-
try {
|
|
2941
|
-
const res = await fetch(url);
|
|
2942
|
-
if (!res.ok) {
|
|
2943
|
-
console.error(chalk.red(`Error: Audit endpoint returned ${res.status}`));
|
|
2944
|
-
process.exit(1);
|
|
2945
|
-
}
|
|
2946
|
-
data = await res.json();
|
|
2947
|
-
}
|
|
2948
|
-
catch (err) {
|
|
4184
|
+
async function handleAudit(options) {
|
|
4185
|
+
let data = await fetchAuditResult();
|
|
4186
|
+
if (!data) {
|
|
2949
4187
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
2950
|
-
console.error(chalk.dim(` ${err.message}`));
|
|
2951
4188
|
process.exit(1);
|
|
2952
4189
|
}
|
|
2953
|
-
|
|
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).
|
|
4194
|
+
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
4195
|
+
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
4196
|
+
let autoRemediationFailed = false;
|
|
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
|
|
4201
|
+
try {
|
|
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
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
catch {
|
|
4216
|
+
// Fall through — re-fetch will show remaining issues
|
|
4217
|
+
}
|
|
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 });
|
|
4221
|
+
if (!data) {
|
|
4222
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
4223
|
+
process.exit(1);
|
|
4224
|
+
}
|
|
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
|
|
4270
|
+
const incompleteAfterFix = data.incompleteEntities || [];
|
|
4271
|
+
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
4272
|
+
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
4273
|
+
autoRemediationFailed = true;
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
let { components, functions, summary } = data;
|
|
2954
4277
|
console.log();
|
|
2955
4278
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
2956
4279
|
console.log();
|
|
2957
4280
|
// Components
|
|
2958
4281
|
if (components.length > 0) {
|
|
4282
|
+
// Build name frequency map for disambiguation
|
|
4283
|
+
const componentNameCounts = new Map();
|
|
4284
|
+
for (const c of components) {
|
|
4285
|
+
componentNameCounts.set(c.name, (componentNameCounts.get(c.name) || 0) + 1);
|
|
4286
|
+
}
|
|
2959
4287
|
console.log(chalk.bold('Components (scenarios):'));
|
|
2960
4288
|
for (const c of components) {
|
|
2961
|
-
const icon = c.status === 'ok'
|
|
4289
|
+
const icon = c.status === 'ok'
|
|
4290
|
+
? chalk.green('✓')
|
|
4291
|
+
: c.status === 'needs_recapture'
|
|
4292
|
+
? chalk.yellow('↻')
|
|
4293
|
+
: chalk.red('✗');
|
|
2962
4294
|
let detail;
|
|
2963
4295
|
if (c.status === 'has_errors') {
|
|
2964
4296
|
detail = chalk.red(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''} but has client errors`);
|
|
2965
4297
|
}
|
|
4298
|
+
else if (c.status === 'needs_recapture') {
|
|
4299
|
+
detail = chalk.yellow(` — ${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''}, needs recapture`);
|
|
4300
|
+
}
|
|
2966
4301
|
else if (c.status === 'ok') {
|
|
2967
4302
|
detail = chalk.dim(` (${c.scenarioCount} scenario${c.scenarioCount !== 1 ? 's' : ''})`);
|
|
2968
4303
|
}
|
|
2969
4304
|
else {
|
|
2970
4305
|
detail = chalk.red(' — no scenarios registered');
|
|
4306
|
+
if (c.hint) {
|
|
4307
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
4308
|
+
}
|
|
2971
4309
|
}
|
|
2972
|
-
|
|
4310
|
+
// Show file path for failing components always, for OK only when name is ambiguous
|
|
4311
|
+
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
4312
|
+
const showPath = (c.status !== 'ok' && c.status !== 'needs_recapture') || isDuplicate;
|
|
4313
|
+
const pathSuffix = showPath && c.filePath ? chalk.dim(` (${c.filePath})`) : '';
|
|
4314
|
+
console.log(` ${icon} ${c.name}${pathSuffix}${detail}`);
|
|
2973
4315
|
if (c.clientErrors && c.clientErrors.length > 0) {
|
|
2974
4316
|
for (const err of c.clientErrors.slice(0, 3)) {
|
|
2975
4317
|
console.log(chalk.red(` → ${err}`));
|
|
@@ -2989,7 +4331,21 @@ async function handleAudit() {
|
|
|
2989
4331
|
let detail;
|
|
2990
4332
|
switch (f.status) {
|
|
2991
4333
|
case 'ok':
|
|
2992
|
-
|
|
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
|
+
}
|
|
4340
|
+
break;
|
|
4341
|
+
case 'runner_error':
|
|
4342
|
+
detail = chalk.red(` — test runner crashed: ${f.testFile}`);
|
|
4343
|
+
if (f.errorMessage) {
|
|
4344
|
+
detail += `\n ${chalk.red(f.errorMessage)}`;
|
|
4345
|
+
detail += `\n ${chalk.yellow('Note: This is NOT a test failure — the test runner itself could not execute.')}`;
|
|
4346
|
+
const ctx = getTechStackContext(process.cwd());
|
|
4347
|
+
detail += `\n ${chalk.yellow('Try running the test manually: ' + ctx.testRunCommand + ' ' + f.testFile)}`;
|
|
4348
|
+
}
|
|
2993
4349
|
break;
|
|
2994
4350
|
case 'failing':
|
|
2995
4351
|
detail = chalk.red(` — tests failing: ${f.testFile}`);
|
|
@@ -2999,13 +4355,23 @@ async function handleAudit() {
|
|
|
2999
4355
|
break;
|
|
3000
4356
|
case 'missing':
|
|
3001
4357
|
default:
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
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
|
+
}
|
|
3005
4368
|
break;
|
|
3006
4369
|
}
|
|
3007
4370
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
3008
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
|
+
}
|
|
3009
4375
|
console.log();
|
|
3010
4376
|
}
|
|
3011
4377
|
// Missing from glossary
|
|
@@ -3015,7 +4381,146 @@ async function handleAudit() {
|
|
|
3015
4381
|
for (const m of missingFromGlossary) {
|
|
3016
4382
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3017
4383
|
}
|
|
3018
|
-
|
|
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
|
+
}
|
|
4393
|
+
console.log();
|
|
4394
|
+
}
|
|
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.
|
|
4399
|
+
const incompleteEntities = data.incompleteEntities || [];
|
|
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());
|
|
4405
|
+
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
4406
|
+
for (const e of incompleteEntities) {
|
|
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
|
+
}
|
|
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';
|
|
4453
|
+
if (autoRemediationFailed) {
|
|
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.`));
|
|
4456
|
+
}
|
|
4457
|
+
else {
|
|
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.'));
|
|
4463
|
+
}
|
|
4464
|
+
console.log();
|
|
4465
|
+
}
|
|
4466
|
+
// Miscategorized scenarios (component scenarios using real page URLs)
|
|
4467
|
+
const miscategorizedScenarios = data.miscategorizedScenarios || [];
|
|
4468
|
+
if (miscategorizedScenarios.length > 0) {
|
|
4469
|
+
console.log(chalk.bold('Miscategorized scenarios (component with page URL):'));
|
|
4470
|
+
for (const m of miscategorizedScenarios) {
|
|
4471
|
+
console.log(` ${chalk.red('✗')} ${m.componentName} at ${chalk.dim(m.url)} — ${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''}`);
|
|
4472
|
+
for (const name of m.scenarioNames) {
|
|
4473
|
+
console.log(chalk.dim(` "${name}"`));
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
console.log(chalk.yellow(' Component scenarios must use isolation routes (/isolated-components/...).'));
|
|
4477
|
+
console.log(chalk.yellow(' Either re-register as app scenarios (remove componentName, add pageFilePath)'));
|
|
4478
|
+
console.log(chalk.yellow(' or re-register with an isolation URL.'));
|
|
4479
|
+
console.log();
|
|
4480
|
+
}
|
|
4481
|
+
// Scenarios needing recapture (entity or dependency tree changed)
|
|
4482
|
+
const scenariosNeedingRecapture = data.scenariosNeedingRecapture || [];
|
|
4483
|
+
if (scenariosNeedingRecapture.length > 0) {
|
|
4484
|
+
console.log(chalk.bold('Scenarios needing recapture (dependency tree changed):'));
|
|
4485
|
+
for (const s of scenariosNeedingRecapture) {
|
|
4486
|
+
const reason = s.status.status === 'impacted' && s.status.impactedBy?.length
|
|
4487
|
+
? `impacted by: ${s.status.impactedBy.map((d) => `${d.name} [${d.changeType}]`).join(', ')}`
|
|
4488
|
+
: `${s.status.status}`;
|
|
4489
|
+
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
4490
|
+
}
|
|
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
|
+
}
|
|
4511
|
+
console.log();
|
|
4512
|
+
}
|
|
4513
|
+
// Duplicate glossary names (warning, not a failure)
|
|
4514
|
+
const duplicateNames = data.duplicateNames || [];
|
|
4515
|
+
if (duplicateNames.length > 0) {
|
|
4516
|
+
console.log(chalk.bold('Duplicate names in glossary (confusing for audit):'));
|
|
4517
|
+
for (const dn of duplicateNames) {
|
|
4518
|
+
console.log(` ${chalk.yellow('⚠')} "${dn.name}" appears ${dn.filePaths.length} times:`);
|
|
4519
|
+
for (const fp of dn.filePaths) {
|
|
4520
|
+
console.log(` ${fp}`);
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
console.log(chalk.yellow(' Fix: remove duplicate entries or rename them to be unique in .codeyam/glossary.json'));
|
|
3019
4524
|
console.log();
|
|
3020
4525
|
}
|
|
3021
4526
|
// Summary
|
|
@@ -3038,28 +4543,135 @@ async function handleAudit() {
|
|
|
3038
4543
|
if (summary.functionsFailing > 0) {
|
|
3039
4544
|
parts.push(`${summary.functionsFailing} function${summary.functionsFailing !== 1 ? 's' : ''} with failing tests`);
|
|
3040
4545
|
}
|
|
4546
|
+
if (summary.functionsRunnerError > 0) {
|
|
4547
|
+
parts.push(`${summary.functionsRunnerError} function${summary.functionsRunnerError !== 1 ? 's' : ''} with test runner errors (not test failures — see details above)`);
|
|
4548
|
+
}
|
|
3041
4549
|
if (summary.functionsNameMismatch > 0) {
|
|
3042
4550
|
parts.push(`${summary.functionsNameMismatch} function${summary.functionsNameMismatch !== 1 ? 's' : ''} with test name mismatch (missing top-level describe)`);
|
|
3043
4551
|
}
|
|
3044
4552
|
if (summary.missingFromGlossary > 0) {
|
|
3045
4553
|
parts.push(`${summary.missingFromGlossary} file${summary.missingFromGlossary !== 1 ? 's' : ''} with scenarios not in glossary`);
|
|
3046
4554
|
}
|
|
4555
|
+
if (summary.incompleteEntities > 0) {
|
|
4556
|
+
parts.push(`${summary.incompleteEntities} entit${summary.incompleteEntities !== 1 ? 'ies' : 'y'} need import analysis`);
|
|
4557
|
+
}
|
|
4558
|
+
if (summary.unassociatedScenarios > 0) {
|
|
4559
|
+
parts.push(`${summary.unassociatedScenarios} component${summary.unassociatedScenarios !== 1 ? 's' : ''} with scenarios missing entity link`);
|
|
4560
|
+
}
|
|
4561
|
+
if (summary.miscategorizedScenarios > 0) {
|
|
4562
|
+
parts.push(`${summary.miscategorizedScenarios} component${summary.miscategorizedScenarios !== 1 ? 's' : ''} using page URLs instead of isolation routes`);
|
|
4563
|
+
}
|
|
4564
|
+
if (summary.scenariosNeedingRecapture > 0) {
|
|
4565
|
+
parts.push(`${summary.scenariosNeedingRecapture} scenario${summary.scenariosNeedingRecapture !== 1 ? 's' : ''} need recapture`);
|
|
4566
|
+
}
|
|
3047
4567
|
console.log(chalk.red.bold('Audit failed: ') + parts.join(', '));
|
|
3048
4568
|
}
|
|
3049
4569
|
console.log();
|
|
3050
4570
|
if (!allOk) {
|
|
3051
4571
|
process.exit(1);
|
|
3052
4572
|
}
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
4573
|
+
}
|
|
4574
|
+
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
4575
|
+
/**
|
|
4576
|
+
* `codeyam editor recapture-stale`
|
|
4577
|
+
*
|
|
4578
|
+
* Identifies all scenarios whose entity (or dependency) has changed but
|
|
4579
|
+
* whose screenshot hasn't been recaptured this session, then recaptures
|
|
4580
|
+
* them in batch.
|
|
4581
|
+
*/
|
|
4582
|
+
async function handleRecaptureStale() {
|
|
4583
|
+
const port = getServerPort();
|
|
4584
|
+
const url = `http://localhost:${port}/api/editor-recapture-stale`;
|
|
4585
|
+
console.log();
|
|
4586
|
+
console.log(chalk.bold.cyan('━━━ Recapture Stale Scenarios ━━━'));
|
|
4587
|
+
console.log();
|
|
4588
|
+
let res;
|
|
3057
4589
|
try {
|
|
3058
|
-
await
|
|
4590
|
+
res = await fetch(url, { method: 'POST' });
|
|
3059
4591
|
}
|
|
3060
|
-
catch {
|
|
3061
|
-
|
|
3062
|
-
console.
|
|
4592
|
+
catch (err) {
|
|
4593
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
4594
|
+
console.error(chalk.dim(` ${err.message}`));
|
|
4595
|
+
process.exit(1);
|
|
4596
|
+
}
|
|
4597
|
+
if (!res.ok) {
|
|
4598
|
+
const body = await res.json().catch(() => null);
|
|
4599
|
+
console.error(chalk.red(`Error: Recapture endpoint returned ${res.status}`));
|
|
4600
|
+
if (body?.error)
|
|
4601
|
+
console.error(chalk.red(` ${body.error}`));
|
|
4602
|
+
process.exit(1);
|
|
4603
|
+
}
|
|
4604
|
+
// Handle JSON response (early returns like "no changes" / "all up to date")
|
|
4605
|
+
const contentType = res.headers.get('content-type') || '';
|
|
4606
|
+
if (contentType.includes('application/json')) {
|
|
4607
|
+
const data = await res.json();
|
|
4608
|
+
if (data.note) {
|
|
4609
|
+
console.log(chalk.dim(data.note));
|
|
4610
|
+
}
|
|
4611
|
+
else if (data.total === 0) {
|
|
4612
|
+
console.log(chalk.green('All scenarios are up to date.'));
|
|
4613
|
+
}
|
|
4614
|
+
console.log();
|
|
4615
|
+
return;
|
|
4616
|
+
}
|
|
4617
|
+
// Stream NDJSON progress events
|
|
4618
|
+
let total = 0;
|
|
4619
|
+
let recapturedCount = 0;
|
|
4620
|
+
let failedCount = 0;
|
|
4621
|
+
const reader = res.body?.getReader();
|
|
4622
|
+
if (!reader) {
|
|
4623
|
+
console.error(chalk.red('Error: No response body'));
|
|
4624
|
+
process.exit(1);
|
|
4625
|
+
}
|
|
4626
|
+
const decoder = new TextDecoder();
|
|
4627
|
+
let buffer = '';
|
|
4628
|
+
while (true) {
|
|
4629
|
+
const { done, value } = await reader.read();
|
|
4630
|
+
if (done)
|
|
4631
|
+
break;
|
|
4632
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4633
|
+
const lines = buffer.split('\n');
|
|
4634
|
+
buffer = lines.pop() || ''; // Keep incomplete last line in buffer
|
|
4635
|
+
for (const line of lines) {
|
|
4636
|
+
if (!line.trim())
|
|
4637
|
+
continue;
|
|
4638
|
+
try {
|
|
4639
|
+
const event = JSON.parse(line);
|
|
4640
|
+
switch (event.type) {
|
|
4641
|
+
case 'start':
|
|
4642
|
+
total = event.total;
|
|
4643
|
+
console.log(`Found ${total} stale scenario(s). Recapturing...\n`);
|
|
4644
|
+
break;
|
|
4645
|
+
case 'capturing':
|
|
4646
|
+
process.stdout.write(chalk.dim(` … ${event.name}`));
|
|
4647
|
+
break;
|
|
4648
|
+
case 'success':
|
|
4649
|
+
// Clear the "capturing" line and print success
|
|
4650
|
+
process.stdout.write('\r\x1b[K');
|
|
4651
|
+
console.log(` ${chalk.green('✓')} ${event.name}`);
|
|
4652
|
+
recapturedCount++;
|
|
4653
|
+
break;
|
|
4654
|
+
case 'failure':
|
|
4655
|
+
process.stdout.write('\r\x1b[K');
|
|
4656
|
+
console.log(` ${chalk.red('✗')} ${event.name} — ${chalk.dim(event.error)}`);
|
|
4657
|
+
failedCount++;
|
|
4658
|
+
break;
|
|
4659
|
+
case 'done':
|
|
4660
|
+
// Final summary
|
|
4661
|
+
console.log();
|
|
4662
|
+
const color = failedCount > 0 ? chalk.yellow : chalk.green;
|
|
4663
|
+
console.log(color(`Recaptured ${recapturedCount}/${total} scenario(s).`));
|
|
4664
|
+
console.log();
|
|
4665
|
+
break;
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
catch {
|
|
4669
|
+
// Skip unparseable lines
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
if (failedCount > 0) {
|
|
4674
|
+
process.exit(1);
|
|
3063
4675
|
}
|
|
3064
4676
|
}
|
|
3065
4677
|
// ─── Scenarios subcommand ─────────────────────────────────────────────
|
|
@@ -3164,20 +4776,22 @@ async function handleScenarioCoverage() {
|
|
|
3164
4776
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3165
4777
|
try {
|
|
3166
4778
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3167
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4779
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3168
4780
|
const db = getDatabase();
|
|
3169
4781
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3170
4782
|
if (backfillCount > 0) {
|
|
3171
4783
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3172
4784
|
await handleAnalyzeImports({ silent: true });
|
|
3173
4785
|
// Run backfill after analysis
|
|
3174
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4786
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3175
4787
|
const entities = await loadEntities({});
|
|
3176
4788
|
if (entities && entities.length > 0) {
|
|
3177
4789
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
3178
4790
|
sha: e.sha,
|
|
3179
4791
|
name: e.name,
|
|
3180
4792
|
filePath: e.filePath || '',
|
|
4793
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
4794
|
+
e.metadata?.namedExport === false,
|
|
3181
4795
|
})));
|
|
3182
4796
|
}
|
|
3183
4797
|
}
|
|
@@ -3307,7 +4921,7 @@ async function handleTemplate() {
|
|
|
3307
4921
|
console.log(chalk.green(' Git initialized.'));
|
|
3308
4922
|
}
|
|
3309
4923
|
// 4. Run codeyam init
|
|
3310
|
-
console.log(chalk.bold('
|
|
4924
|
+
console.log(chalk.bold('Initializing project...'));
|
|
3311
4925
|
await initCommand.handler({
|
|
3312
4926
|
force: true,
|
|
3313
4927
|
'keep-server': true,
|
|
@@ -3316,7 +4930,7 @@ async function handleTemplate() {
|
|
|
3316
4930
|
_: [],
|
|
3317
4931
|
});
|
|
3318
4932
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
3319
|
-
// 5. Verify config has startCommand
|
|
4933
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
3320
4934
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3321
4935
|
if (fs.existsSync(configPath)) {
|
|
3322
4936
|
try {
|
|
@@ -3327,6 +4941,30 @@ async function handleTemplate() {
|
|
|
3327
4941
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
3328
4942
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
3329
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));
|
|
3330
4968
|
}
|
|
3331
4969
|
catch {
|
|
3332
4970
|
// Config parse error is non-fatal
|
|
@@ -3361,7 +4999,15 @@ async function handleTemplate() {
|
|
|
3361
4999
|
}
|
|
3362
5000
|
console.log();
|
|
3363
5001
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
3364
|
-
|
|
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
|
+
}
|
|
3365
5011
|
}
|
|
3366
5012
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
3367
5013
|
/**
|
|
@@ -3386,7 +5032,7 @@ async function handleSync() {
|
|
|
3386
5032
|
// fall through
|
|
3387
5033
|
}
|
|
3388
5034
|
if (!projectSlug) {
|
|
3389
|
-
console.error(chalk.red('Error: No project slug found. Run codeyam
|
|
5035
|
+
console.error(chalk.red('Error: No project slug found. Run `codeyam editor template` to initialize the project.'));
|
|
3390
5036
|
process.exit(1);
|
|
3391
5037
|
}
|
|
3392
5038
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
@@ -3570,7 +5216,7 @@ function handleEditorDebug(args) {
|
|
|
3570
5216
|
scenarios.push({
|
|
3571
5217
|
id: 'overview-with-state',
|
|
3572
5218
|
title: 'Cycle overview (project, with state)',
|
|
3573
|
-
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(
|
|
5219
|
+
render: () => withTempRoot(true, (tempRoot) => captureOutput(() => printCycleOverview(tempRoot, makeState(6, feature)))),
|
|
3574
5220
|
});
|
|
3575
5221
|
}
|
|
3576
5222
|
const stepFns = {
|
|
@@ -3590,8 +5236,10 @@ function handleEditorDebug(args) {
|
|
|
3590
5236
|
14: printStep14,
|
|
3591
5237
|
15: printStep15,
|
|
3592
5238
|
16: printStep16,
|
|
5239
|
+
17: printStep17,
|
|
5240
|
+
18: printStep18,
|
|
3593
5241
|
};
|
|
3594
|
-
for (let step = 1; step <=
|
|
5242
|
+
for (let step = 1; step <= 18; step++) {
|
|
3595
5243
|
const stepId = `step-${step}`;
|
|
3596
5244
|
if (!wants(stepId))
|
|
3597
5245
|
continue;
|
|
@@ -3611,7 +5259,7 @@ function handleEditorDebug(args) {
|
|
|
3611
5259
|
if (step === 2) {
|
|
3612
5260
|
scenarios.push({
|
|
3613
5261
|
id: 'step-2-scaffold',
|
|
3614
|
-
title: 'Step 2 (
|
|
5262
|
+
title: 'Step 2 (Prepare) — scaffold flow (no project)',
|
|
3615
5263
|
render: () => withTempRoot(false, (tempRoot) => captureOutput(() => printStep2(tempRoot, feature))),
|
|
3616
5264
|
});
|
|
3617
5265
|
}
|
|
@@ -3664,14 +5312,205 @@ function handleEditorDebug(args) {
|
|
|
3664
5312
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
3665
5313
|
console.log();
|
|
3666
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
|
+
}
|
|
3667
5506
|
// ─── Command definition ───────────────────────────────────────────────
|
|
3668
5507
|
const editorCommand = {
|
|
3669
5508
|
command: 'editor [step] [json]',
|
|
3670
5509
|
describe: 'Editor mode guided workflow',
|
|
3671
5510
|
builder: (yargs) => {
|
|
3672
5511
|
const stepDescription = IS_INTERNAL_BUILD
|
|
3673
|
-
? 'Step number (1-
|
|
3674
|
-
: 'Step number (1-
|
|
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)';
|
|
3675
5514
|
let builder = yargs
|
|
3676
5515
|
.positional('step', {
|
|
3677
5516
|
type: 'string',
|
|
@@ -3702,12 +5541,17 @@ const editorCommand = {
|
|
|
3702
5541
|
alias: 'p',
|
|
3703
5542
|
describe: 'Port to run the web server on',
|
|
3704
5543
|
default: 3111,
|
|
5544
|
+
})
|
|
5545
|
+
.option('fix', {
|
|
5546
|
+
type: 'boolean',
|
|
5547
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
5548
|
+
default: false,
|
|
3705
5549
|
});
|
|
3706
5550
|
if (IS_INTERNAL_BUILD) {
|
|
3707
5551
|
builder = builder
|
|
3708
5552
|
.option('target', {
|
|
3709
5553
|
type: 'string',
|
|
3710
|
-
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-
|
|
5554
|
+
describe: 'Debug target (setup, overview, overview-with-state, step-1..step-18, or comma-separated list)',
|
|
3711
5555
|
})
|
|
3712
5556
|
.option('resume', {
|
|
3713
5557
|
type: 'boolean',
|
|
@@ -3723,13 +5567,27 @@ const editorCommand = {
|
|
|
3723
5567
|
describe: 'Debug: output directory for the bundle',
|
|
3724
5568
|
});
|
|
3725
5569
|
}
|
|
3726
|
-
|
|
5570
|
+
// Allow extra positional args for subcommands like `isolate A B C`
|
|
5571
|
+
// without yargs rejecting them as unknown.
|
|
5572
|
+
return builder.strict(false);
|
|
3727
5573
|
},
|
|
3728
5574
|
handler: async (argv) => {
|
|
3729
5575
|
const root = getProjectRoot();
|
|
3730
5576
|
// API subcommands: preview, show-results, hide-results, commit,
|
|
3731
5577
|
// journal, journal-update, dev-server, client-errors
|
|
3732
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
|
+
}
|
|
3733
5591
|
const port = getServerPort();
|
|
3734
5592
|
try {
|
|
3735
5593
|
const request = buildEditorApiRequest(argv.step, argv.json || undefined);
|
|
@@ -3749,6 +5607,12 @@ const editorCommand = {
|
|
|
3749
5607
|
console.log(JSON.stringify(result.data, null, 2));
|
|
3750
5608
|
}
|
|
3751
5609
|
}
|
|
5610
|
+
// After a successful commit, remind Claude to continue to step 17
|
|
5611
|
+
if (argv.step === 'commit' && result.ok) {
|
|
5612
|
+
console.log();
|
|
5613
|
+
console.log(chalk.green('Commit done. Now run: ') +
|
|
5614
|
+
chalk.bold('codeyam editor 17'));
|
|
5615
|
+
}
|
|
3752
5616
|
}
|
|
3753
5617
|
catch (err) {
|
|
3754
5618
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
@@ -3757,14 +5621,73 @@ const editorCommand = {
|
|
|
3757
5621
|
}
|
|
3758
5622
|
return;
|
|
3759
5623
|
}
|
|
5624
|
+
// Subcommand: codeyam editor handoff '{"summary":"..."}'
|
|
5625
|
+
if (argv.step === 'handoff') {
|
|
5626
|
+
await handleHandoff(argv.json || '');
|
|
5627
|
+
return;
|
|
5628
|
+
}
|
|
3760
5629
|
// Subcommand: codeyam editor register '{"name":"..."}'
|
|
3761
5630
|
if (argv.step === 'register') {
|
|
3762
5631
|
await handleRegister(argv.json || '');
|
|
3763
5632
|
return;
|
|
3764
5633
|
}
|
|
3765
|
-
// Subcommand: codeyam editor
|
|
5634
|
+
// Subcommand: codeyam editor glossary-add '{"name":"...", ...}'
|
|
5635
|
+
if (argv.step === 'glossary-add') {
|
|
5636
|
+
await handleGlossaryAdd(argv.json || '');
|
|
5637
|
+
return;
|
|
5638
|
+
}
|
|
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 ...]
|
|
3766
5682
|
if (argv.step === 'analyze-imports') {
|
|
3767
|
-
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 || '');
|
|
3768
5691
|
return;
|
|
3769
5692
|
}
|
|
3770
5693
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -3772,9 +5695,9 @@ const editorCommand = {
|
|
|
3772
5695
|
await handleDependents(argv.json || '');
|
|
3773
5696
|
return;
|
|
3774
5697
|
}
|
|
3775
|
-
// Subcommand: codeyam editor audit
|
|
5698
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
3776
5699
|
if (argv.step === 'audit') {
|
|
3777
|
-
await handleAudit();
|
|
5700
|
+
await handleAudit({ fix: argv.fix || false });
|
|
3778
5701
|
return;
|
|
3779
5702
|
}
|
|
3780
5703
|
// Subcommand: codeyam editor scenarios
|
|
@@ -3787,6 +5710,11 @@ const editorCommand = {
|
|
|
3787
5710
|
await handleScenarioCoverage();
|
|
3788
5711
|
return;
|
|
3789
5712
|
}
|
|
5713
|
+
// Subcommand: codeyam editor recapture-stale
|
|
5714
|
+
if (argv.step === 'recapture-stale') {
|
|
5715
|
+
await handleRecaptureStale();
|
|
5716
|
+
return;
|
|
5717
|
+
}
|
|
3790
5718
|
// Subcommand: codeyam editor change <feature>
|
|
3791
5719
|
if (argv.step === 'change') {
|
|
3792
5720
|
handleChange(argv.json || '');
|
|
@@ -3802,19 +5730,21 @@ const editorCommand = {
|
|
|
3802
5730
|
await handleValidateSeed(argv.json || '');
|
|
3803
5731
|
return;
|
|
3804
5732
|
}
|
|
5733
|
+
// Subcommand: codeyam editor design-system <id>
|
|
5734
|
+
if (argv.step === 'design-system') {
|
|
5735
|
+
handleDesignSystem(argv.json || '');
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
3805
5738
|
// Subcommand: codeyam editor delete <scenarioId>
|
|
3806
5739
|
if (argv.step === 'delete') {
|
|
3807
5740
|
await handleDelete(argv.json || '');
|
|
3808
5741
|
return;
|
|
3809
5742
|
}
|
|
3810
5743
|
// Subcommand: codeyam editor isolate "StarRating CategoryBadge DrinkCard"
|
|
5744
|
+
// Also supports: codeyam editor isolate StarRating CategoryBadge DrinkCard
|
|
3811
5745
|
if (argv.step === 'isolate') {
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
names.push(...argv.json.split(/[\s,]+/).filter(Boolean));
|
|
3815
|
-
// Collect any extra positional args (yargs puts them in argv._)
|
|
3816
|
-
const extras = argv._.filter((a) => typeof a === 'string' && a !== 'editor');
|
|
3817
|
-
names.push(...extras);
|
|
5746
|
+
const { parseIsolateArgs } = await import('./editorIsolateArgs.js');
|
|
5747
|
+
const names = parseIsolateArgs(argv.json, argv._);
|
|
3818
5748
|
handleIsolate(names);
|
|
3819
5749
|
return;
|
|
3820
5750
|
}
|
|
@@ -3849,18 +5779,13 @@ const editorCommand = {
|
|
|
3849
5779
|
}
|
|
3850
5780
|
else {
|
|
3851
5781
|
const state = readState(root);
|
|
3852
|
-
// Clear prompt file when feature is done (step 16) so the hook
|
|
3853
|
-
// can capture the next feature request from the user.
|
|
3854
|
-
if (state?.step === 16) {
|
|
3855
|
-
clearEditorUserPrompt(root);
|
|
3856
|
-
}
|
|
3857
5782
|
printCycleOverview(root, state);
|
|
3858
5783
|
}
|
|
3859
5784
|
return;
|
|
3860
5785
|
}
|
|
3861
5786
|
const step = argv.step ? parseInt(argv.step, 10) : undefined;
|
|
3862
|
-
if (step != null && (isNaN(step) || step < 1 || step >
|
|
3863
|
-
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-
|
|
5787
|
+
if (step != null && (isNaN(step) || step < 1 || step > 18)) {
|
|
5788
|
+
console.error(chalk.red(`Error: Invalid step "${argv.step}". Must be 1-18.`));
|
|
3864
5789
|
process.exit(1);
|
|
3865
5790
|
}
|
|
3866
5791
|
if (step == null) {
|
|
@@ -3883,10 +5808,18 @@ const editorCommand = {
|
|
|
3883
5808
|
try {
|
|
3884
5809
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3885
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
|
+
}
|
|
3886
5818
|
if (!projectSlug) {
|
|
3887
|
-
errorLog('Missing project slug. Try reinitializing with: codeyam
|
|
5819
|
+
errorLog('Missing project slug. Try reinitializing with: `codeyam editor template`');
|
|
3888
5820
|
return;
|
|
3889
5821
|
}
|
|
5822
|
+
console.log(chalk.dim(` CodeYam Editor v${getDisplayVersion()}`));
|
|
3890
5823
|
const connectionOk = await withoutSpinner(() => testEnvironment());
|
|
3891
5824
|
if (!connectionOk) {
|
|
3892
5825
|
errorLog('Environment validation failed');
|
|
@@ -4002,6 +5935,52 @@ const editorCommand = {
|
|
|
4002
5935
|
catch {
|
|
4003
5936
|
// Non-fatal — migration failure shouldn't block editor startup
|
|
4004
5937
|
}
|
|
5938
|
+
// Auto-seed on fresh clone: if no scenario has ever been activated
|
|
5939
|
+
// (active-scenario.json doesn't exist), seed the application database
|
|
5940
|
+
// with the first application scenario that has seed data.
|
|
5941
|
+
const activeScenarioPath = path.join(projectRoot, '.codeyam', 'active-scenario.json');
|
|
5942
|
+
if (!fs.existsSync(activeScenarioPath)) {
|
|
5943
|
+
try {
|
|
5944
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
5945
|
+
const seedDb = getDb();
|
|
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
|
|
5949
|
+
.selectFrom('editor_scenarios')
|
|
5950
|
+
.select(['id', 'name', 'type'])
|
|
5951
|
+
.where('project_id', '=', project.id)
|
|
5952
|
+
.where('url', '=', '/')
|
|
5953
|
+
.where('component_name', 'is', null)
|
|
5954
|
+
.orderBy('created_at', 'asc')
|
|
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());
|
|
5964
|
+
if (appScenario) {
|
|
5965
|
+
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
5966
|
+
if (fs.existsSync(seedFile)) {
|
|
5967
|
+
const { switchActiveScenario } = await import('../utils/editorScenarioSwitch.js');
|
|
5968
|
+
const seedResult = await switchActiveScenario({
|
|
5969
|
+
scenarioId: appScenario.id,
|
|
5970
|
+
scenarioName: appScenario.name || undefined,
|
|
5971
|
+
scenarioType: appScenario.type || undefined,
|
|
5972
|
+
projectRoot,
|
|
5973
|
+
});
|
|
5974
|
+
if (seedResult.seedResult?.success) {
|
|
5975
|
+
console.log(chalk.green(` Auto-seeded database with scenario: ${appScenario.name || appScenario.id}`));
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
catch {
|
|
5981
|
+
// Non-fatal — auto-seed is best-effort
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
4005
5984
|
// `codeyam editor` (no step) always implies editor mode.
|
|
4006
5985
|
// The empty-folder heuristic is no longer needed here — running this
|
|
4007
5986
|
// command IS the signal. We still detect empty folders so that
|
|
@@ -4124,7 +6103,7 @@ const editorCommand = {
|
|
|
4124
6103
|
if (!needsAnalysis) {
|
|
4125
6104
|
try {
|
|
4126
6105
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4127
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
6106
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4128
6107
|
const db = getDatabase();
|
|
4129
6108
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4130
6109
|
if (backfillCount > 0) {
|
|
@@ -4146,6 +6125,48 @@ const editorCommand = {
|
|
|
4146
6125
|
}
|
|
4147
6126
|
}
|
|
4148
6127
|
}
|
|
6128
|
+
// Backfill page_file_path for application scenarios that have a URL but
|
|
6129
|
+
// no page_file_path. This resolves the file from the URL using Next.js
|
|
6130
|
+
// routing conventions, enabling syncScenarioEntityShas to link them to
|
|
6131
|
+
// entities. Covers fresh clones where JSON files lack pageFilePath.
|
|
6132
|
+
try {
|
|
6133
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
6134
|
+
const pfpDb = getDb();
|
|
6135
|
+
const unresolved = await pfpDb
|
|
6136
|
+
.selectFrom('editor_scenarios')
|
|
6137
|
+
.select(['id', 'url'])
|
|
6138
|
+
.where('project_id', '=', project.id)
|
|
6139
|
+
.where('component_name', 'is', null)
|
|
6140
|
+
.where('page_file_path', 'is', null)
|
|
6141
|
+
.where('url', 'is not', null)
|
|
6142
|
+
.execute();
|
|
6143
|
+
if (unresolved.length > 0) {
|
|
6144
|
+
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
6145
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
6146
|
+
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
6147
|
+
let pfpResolved = 0;
|
|
6148
|
+
for (const row of unresolved) {
|
|
6149
|
+
const r = row;
|
|
6150
|
+
if (!r.url)
|
|
6151
|
+
continue;
|
|
6152
|
+
const matched = matchUrlToPageFile(r.url, pfpFiles);
|
|
6153
|
+
if (matched) {
|
|
6154
|
+
await pfpDb
|
|
6155
|
+
.updateTable('editor_scenarios')
|
|
6156
|
+
.set({ page_file_path: matched })
|
|
6157
|
+
.where('id', '=', r.id)
|
|
6158
|
+
.execute();
|
|
6159
|
+
pfpResolved++;
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
if (pfpResolved > 0) {
|
|
6163
|
+
console.log(chalk.green(` Resolved page_file_path for ${pfpResolved} scenario(s) from URL`));
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
catch {
|
|
6168
|
+
/* Non-fatal — page_file_path backfill from URL */
|
|
6169
|
+
}
|
|
4149
6170
|
// Backfill entity_sha on scenarios that were synced before entities existed.
|
|
4150
6171
|
// This runs independently of analyze-imports so fresh clones and file-synced
|
|
4151
6172
|
// scenarios get linked to their entities even when auto-analyze doesn't trigger.
|
|
@@ -4158,6 +6179,8 @@ const editorCommand = {
|
|
|
4158
6179
|
sha: e.sha,
|
|
4159
6180
|
name: e.name,
|
|
4160
6181
|
filePath: e.filePath || '',
|
|
6182
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
6183
|
+
e.metadata?.namedExport === false,
|
|
4161
6184
|
})));
|
|
4162
6185
|
if (result.updated > 0) {
|
|
4163
6186
|
console.log(chalk.green(` Linked ${result.updated} scenario(s) to their entities`));
|
|
@@ -4202,12 +6225,15 @@ const editorCommand = {
|
|
|
4202
6225
|
// Step 1 is planning-only and may not persist state (no --feature flag).
|
|
4203
6226
|
const skipValidation = step === 2 && argv.feature;
|
|
4204
6227
|
if (!skipValidation) {
|
|
4205
|
-
const stepError = validateStepTransition(step, state?.step ?? null);
|
|
6228
|
+
const stepError = validateStepTransition(step, state?.step ?? null, state?.startedAt, root);
|
|
4206
6229
|
if (stepError) {
|
|
4207
6230
|
console.error(chalk.red(`Error: ${stepError}`));
|
|
4208
6231
|
process.exit(1);
|
|
4209
6232
|
}
|
|
4210
6233
|
}
|
|
6234
|
+
printHandoffContext(root);
|
|
6235
|
+
// Track step progress automatically for provider handoff
|
|
6236
|
+
updateHandoffProgress(root, step);
|
|
4211
6237
|
switch (step) {
|
|
4212
6238
|
case 1: {
|
|
4213
6239
|
const feature = argv.feature || undefined;
|
|
@@ -4242,18 +6268,20 @@ const editorCommand = {
|
|
|
4242
6268
|
case 13:
|
|
4243
6269
|
case 14:
|
|
4244
6270
|
case 15:
|
|
4245
|
-
case 16:
|
|
6271
|
+
case 16:
|
|
6272
|
+
case 17:
|
|
6273
|
+
case 18: {
|
|
4246
6274
|
const feature = argv.feature || state?.feature;
|
|
4247
6275
|
if (!feature) {
|
|
4248
6276
|
console.error(chalk.red('Error: No feature in progress. Run codeyam editor 1 first.'));
|
|
4249
6277
|
process.exit(1);
|
|
4250
6278
|
}
|
|
4251
|
-
// Hard gate: steps
|
|
4252
|
-
if (step >=
|
|
6279
|
+
// Hard gate: steps 10+ require audit to have passed
|
|
6280
|
+
if (step >= 10) {
|
|
4253
6281
|
const auditOk = await checkAuditGate();
|
|
4254
6282
|
if (!auditOk) {
|
|
4255
|
-
|
|
4256
|
-
console.error(chalk.
|
|
6283
|
+
// checkAuditGate() already printed specific failure details above
|
|
6284
|
+
console.error(chalk.red.bold('BLOCKED: The audit has not passed. Fix the issues listed above before proceeding.'));
|
|
4257
6285
|
process.exit(1);
|
|
4258
6286
|
}
|
|
4259
6287
|
}
|
|
@@ -4272,6 +6300,8 @@ const editorCommand = {
|
|
|
4272
6300
|
14: printStep14,
|
|
4273
6301
|
15: printStep15,
|
|
4274
6302
|
16: printStep16,
|
|
6303
|
+
17: printStep17,
|
|
6304
|
+
18: printStep18,
|
|
4275
6305
|
};
|
|
4276
6306
|
stepFns[step](root, feature);
|
|
4277
6307
|
break;
|