@codeyam/codeyam-cli 0.1.0-staging.50df560 → 0.1.0-staging.5370992
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +1 -1
- package/analyzer-template/packages/ai/src/lib/astScopes/methodSemantics.ts +135 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/nodeToSource.ts +19 -0
- package/analyzer-template/packages/ai/src/lib/astScopes/paths.ts +11 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +36 -9
- package/analyzer-template/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.ts +10 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +16 -6
- package/analyzer-template/packages/analyze/index.ts +4 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +28 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +5 -36
- package/analyzer-template/packages/analyze/src/lib/files/analyze/findOrCreateEntity.ts +1 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.ts +21 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +82 -10
- package/analyzer-template/packages/analyze/src/lib/files/analyzeChange.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeInitial.ts +4 -0
- package/analyzer-template/packages/analyze/src/lib/files/analyzeNextRoute.ts +8 -3
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +239 -58
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +1684 -1462
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/database/src/lib/loadAnalysis.ts +7 -1
- package/analyzer-template/packages/database/src/lib/loadEntity.ts +11 -4
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js +7 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadAnalysis.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts +4 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js +4 -4
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntity.js.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts +3 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +27 -0
- package/analyzer-template/project/runMultiScenarioServer.ts +26 -3
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js +23 -3
- package/background/src/lib/virtualized/project/runMultiScenarioServer.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js +47 -0
- package/codeyam-cli/src/commands/__tests__/editor.analyzeImportsArgs.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js +71 -0
- package/codeyam-cli/src/commands/__tests__/editor.auditNoAutoAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js +30 -0
- package/codeyam-cli/src/commands/__tests__/editor.designSystem.test.js.map +1 -0
- package/codeyam-cli/src/commands/editor.js +1014 -194
- 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/data/designSystems.js +27 -0
- package/codeyam-cli/src/data/designSystems.js.map +1 -0
- 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 +1092 -30
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +11 -3
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +140 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js +50 -1
- package/codeyam-cli/src/utils/__tests__/editorSeedAdapter.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +33 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js +302 -0
- package/codeyam-cli/src/utils/__tests__/manualEntityAnalysis.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js +127 -0
- package/codeyam-cli/src/utils/__tests__/registerScenarioResult.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js +217 -0
- package/codeyam-cli/src/utils/__tests__/testRunner.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js +6 -0
- package/codeyam-cli/src/utils/__tests__/webappDetection.test.js.map +1 -1
- package/codeyam-cli/src/utils/analysisRunner.js +28 -1
- package/codeyam-cli/src/utils/analysisRunner.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +11 -1
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/editorApi.js +16 -0
- package/codeyam-cli/src/utils/editorApi.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +248 -16
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +5 -3
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +61 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/editorSeedAdapter.js +42 -2
- package/codeyam-cli/src/utils/editorSeedAdapter.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +31 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/manualEntityAnalysis.js +196 -0
- package/codeyam-cli/src/utils/manualEntityAnalysis.js.map +1 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js +159 -0
- package/codeyam-cli/src/utils/queue/__tests__/job.interactiveStart.test.js.map +1 -0
- package/codeyam-cli/src/utils/queue/job.js +29 -3
- package/codeyam-cli/src/utils/queue/job.js.map +1 -1
- package/codeyam-cli/src/utils/registerScenarioResult.js +52 -0
- package/codeyam-cli/src/utils/registerScenarioResult.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +8 -2
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/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 +199 -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 +98 -0
- package/codeyam-cli/src/webserver/__tests__/api.interactive-switch-scenario.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +30 -11
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +67 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -1
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +3 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -1
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js +34 -0
- package/codeyam-cli/src/webserver/app/routes/api.interactive-switch-scenario.js.map +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-CKeQT5Ty.js → InteractivePreview-DtYTSPL2.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/MiniClaudeChat-CQENLSrF.js +36 -0
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-DUMfcNVK.js → ScenarioViewer-CefgqbCr.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-Bc8BG-Lw.js +34 -0
- package/codeyam-cli/src/webserver/build/client/assets/{_index-BAWd-Xjf.js → _index-C1YkzTAV.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BOARiB-g.js → activity.(_tab)-yH46LLUz.js} +1 -1
- 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/cy-logo-cli-Coe5NhbS.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{cy-logo-cli-CCKUIm0S.svg → cy-logo-cli-DoA97ML3.svg} +2 -2
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C8y4mmyv.js → dev.empty-CRepiabR.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DLM1-ZMt.js +96 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-CluPkvXJ.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-Blfy9UlN.js → entity._sha._-DYJRGiDI.js} +13 -12
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-KTQuL0aj.js → entity._sha.scenarios._scenarioId.dev-wdiwx5-Z.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-C6eeL24i.js → entity._sha.scenarios._scenarioId.fullscreen-BrkN-40Y.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DQM8E7L4.js → entity._sha_.create-scenario-DxfhekTZ.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-CAoXLsQr.js → entity._sha_.edit._scenarioId-CRXJWmpB.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/globals-9EkC9j9I.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/manifest-7e749098.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-BxUQigda.js → root-DGtly3mb.js} +26 -13
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-D9QZKaLJ.js +2 -0
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-CO8xocj3.js +16 -0
- package/codeyam-cli/src/webserver/build/server/assets/{index-CjLhfz6Z.js → index-QKPqlUgg.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/{init-BEqlbI84.js → init-DlspChIk.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-ChzicV-B.js +689 -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 +55 -3
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +27 -3
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +22 -8
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/codeyam-editor-reference.md +8 -6
- package/codeyam-cli/templates/design-systems/clean-dashboard-design-system.md +255 -0
- package/codeyam-cli/templates/design-systems/editorial-design-system.md +267 -0
- package/codeyam-cli/templates/design-systems/mono-brutalist-design-system.md +256 -0
- package/codeyam-cli/templates/design-systems/neo-brutalist-design-system.md +294 -0
- package/codeyam-cli/templates/expo-react-native/MOBILE_SETUP.md +115 -0
- package/codeyam-cli/templates/expo-react-native/__tests__/.gitkeep +0 -0
- package/codeyam-cli/templates/expo-react-native/app/_layout.tsx +3 -2
- 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 +16 -6
- package/codeyam-cli/templates/isolation-route/expo-router.tsx.template +54 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/seed-adapter.ts +47 -34
- package/codeyam-cli/templates/seed-adapters/supabase.ts +14 -5
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +17 -2
- package/package.json +1 -1
- package/packages/ai/src/lib/astScopes/methodSemantics.js +99 -0
- package/packages/ai/src/lib/astScopes/methodSemantics.js.map +1 -1
- package/packages/ai/src/lib/astScopes/nodeToSource.js +16 -0
- package/packages/ai/src/lib/astScopes/nodeToSource.js.map +1 -1
- package/packages/ai/src/lib/astScopes/paths.js +12 -3
- package/packages/ai/src/lib/astScopes/paths.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +27 -10
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js +9 -2
- package/packages/ai/src/lib/dataStructure/equivalencyManagers/ParentScopeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +14 -4
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/analyze/index.js +1 -1
- package/packages/analyze/index.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +16 -2
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +6 -26
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js +1 -0
- package/packages/analyze/src/lib/files/analyze/findOrCreateEntity.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js +14 -0
- package/packages/analyze/src/lib/files/analyze/trackEntityCircularDependencies.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +44 -11
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeChange.js +1 -0
- package/packages/analyze/src/lib/files/analyzeChange.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeInitial.js +1 -0
- package/packages/analyze/src/lib/files/analyzeInitial.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js +5 -1
- package/packages/analyze/src/lib/files/analyzeNextRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +120 -28
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +1368 -1193
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/database/src/lib/loadAnalysis.js +7 -1
- package/packages/database/src/lib/loadAnalysis.js.map +1 -1
- package/packages/database/src/lib/loadEntity.js +4 -4
- package/packages/database/src/lib/loadEntity.js.map +1 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js +22 -1
- package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/Spinner-D0LgAaSa.js +0 -34
- package/codeyam-cli/src/webserver/build/client/assets/cy-logo-cli-DcX-ZS3p.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor.entity.(_sha)-DII1pg_z.js +0 -58
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-oepecPae.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/globals-Yn9W3zp3.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-cdf2c0a7.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/useLastLogLine-BNd5hYuW.js +0 -2
- package/codeyam-cli/src/webserver/build/server/assets/analysisRunner-B_PsTAb1.js +0 -13
- package/codeyam-cli/src/webserver/build/server/assets/server-build-YI63xTu4.js +0 -553
|
@@ -13,6 +13,7 @@ import { installClaudeCodeSkills } from "../utils/install-skills.js";
|
|
|
13
13
|
import { setupClaudeCodeSettings } from "../utils/setupClaudeCodeSettings.js";
|
|
14
14
|
import { ensureAnalyzerFinalized, } from "../utils/analyzerFinalization.js";
|
|
15
15
|
import { APP_FORMATS, TECH_STACKS } from "../data/techStacks.js";
|
|
16
|
+
import { DESIGN_SYSTEMS } from "../data/designSystems.js";
|
|
16
17
|
import { getProjectRoot as getStateProjectRoot } from "../state.js";
|
|
17
18
|
import initCommand from "./init.js";
|
|
18
19
|
import { scanScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../utils/scenariosManifest.js";
|
|
@@ -21,6 +22,7 @@ import { validateSeedData, detectSeedAdapter, validateSeedKeysAgainstPrisma, } f
|
|
|
21
22
|
import { deleteScenarioViaCli } from "../utils/editorDeleteScenario.js";
|
|
22
23
|
import { buildEditorApiRequest, callEditorApi, EDITOR_API_SUBCOMMANDS, } from "../utils/editorApi.js";
|
|
23
24
|
import { parseRegisterArg } from "../utils/parseRegisterArg.js";
|
|
25
|
+
import { classifyRegistrationResult } from "../utils/registerScenarioResult.js";
|
|
24
26
|
import { sanitizeGlossaryEntries } from "../utils/editorLoaderHelpers.js";
|
|
25
27
|
import { readMigrationState, writeMigrationState, advanceToNextPage, completePage, getMigrationResumeInfo, } from "../utils/editorMigration.js";
|
|
26
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -187,6 +189,44 @@ function getProjectDimensions(root) {
|
|
|
187
189
|
return { defaultName: 'Desktop', names: [] };
|
|
188
190
|
}
|
|
189
191
|
}
|
|
192
|
+
function getTechStackContext(root) {
|
|
193
|
+
const state = readState(root);
|
|
194
|
+
const techStackId = state?.techStackId || '';
|
|
195
|
+
const stack = TECH_STACKS.find((s) => s.id === techStackId);
|
|
196
|
+
const isExpo = techStackId === 'expo-react-native';
|
|
197
|
+
const isChromeExt = techStackId === 'chrome-extension-react';
|
|
198
|
+
const isNextjs = techStackId.startsWith('nextjs-') || (!isExpo && !isChromeExt);
|
|
199
|
+
return {
|
|
200
|
+
id: techStackId || 'nextjs-prisma-sqlite',
|
|
201
|
+
isExpo,
|
|
202
|
+
isNextjs,
|
|
203
|
+
isChromeExt,
|
|
204
|
+
hasDatabase: isNextjs,
|
|
205
|
+
testRunner: isNextjs ? 'vitest' : 'jest',
|
|
206
|
+
testRunCommand: isNextjs ? 'npx vitest run' : 'npx jest',
|
|
207
|
+
storageType: isExpo
|
|
208
|
+
? 'asyncStorage'
|
|
209
|
+
: isChromeExt
|
|
210
|
+
? 'chromeStorage'
|
|
211
|
+
: 'prisma',
|
|
212
|
+
routerImport: isExpo
|
|
213
|
+
? 'expo-router'
|
|
214
|
+
: isChromeExt
|
|
215
|
+
? 'react-router-dom'
|
|
216
|
+
: 'next/navigation',
|
|
217
|
+
componentPrimitives: isExpo
|
|
218
|
+
? '<View>, <Text>, <ScrollView>'
|
|
219
|
+
: '<div>, <span>, <h1>',
|
|
220
|
+
rawPrimitivesList: isExpo
|
|
221
|
+
? '<View>, <Text>, <Image>, <ScrollView>, <FlatList>'
|
|
222
|
+
: '<div>, <span>, <h1>, <p>, <img>, <ul>',
|
|
223
|
+
patternsFile: isExpo
|
|
224
|
+
? 'MOBILE_SETUP.md'
|
|
225
|
+
: isChromeExt
|
|
226
|
+
? 'EXTENSION_SETUP.md'
|
|
227
|
+
: 'FEATURE_PATTERNS.md',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
190
230
|
/**
|
|
191
231
|
* Print dimension guidance when the project has multiple screen sizes.
|
|
192
232
|
* Tells Claude to pick the right dimension for the content being previewed
|
|
@@ -210,6 +250,34 @@ function checkbox(text) {
|
|
|
210
250
|
const highlighted = text.replace(/`([^`]+)`/g, (_m, code) => chalk.cyan(code));
|
|
211
251
|
console.log(` ${chalk.dim('[ ]')} ${highlighted}`);
|
|
212
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Instructions for creating/updating .codeyam/data-structure.json.
|
|
255
|
+
* Only prints creation instructions if the file doesn't exist yet.
|
|
256
|
+
* If it exists, reminds Claude to update it if data models changed.
|
|
257
|
+
*/
|
|
258
|
+
function printDataStructureInstructions() {
|
|
259
|
+
const root = getProjectRoot();
|
|
260
|
+
const dsPath = path.join(root, '.codeyam', 'data-structure.json');
|
|
261
|
+
const exists = fs.existsSync(dsPath);
|
|
262
|
+
if (!exists) {
|
|
263
|
+
console.log(chalk.bold('Create the data structure config:'));
|
|
264
|
+
checkbox('Create `.codeyam/data-structure.json` describing all data sources');
|
|
265
|
+
console.log(chalk.dim(" This file tells the editor what data the app uses and how it's stored."));
|
|
266
|
+
console.log(chalk.dim(' The file is a JSON array. Each entry describes one data source:'));
|
|
267
|
+
console.log(chalk.dim(' { "name": "Drink", "description": "...", "category": "datastore", "order": 1,'));
|
|
268
|
+
console.log(chalk.dim(' "fields": [{ "name": "id", "type": "number", "isId": true, "required": true }, ...] }'));
|
|
269
|
+
console.log();
|
|
270
|
+
console.log(chalk.dim(' category: "datastore" for persisted data (DB, localStorage)'));
|
|
271
|
+
console.log(chalk.dim(' "mock-api" for mocked external API responses'));
|
|
272
|
+
console.log(chalk.dim(' order: 1 = primary data store, 2 = secondary, etc.'));
|
|
273
|
+
console.log(chalk.dim(' Do NOT include types only used as component props.'));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
checkbox('If this feature changes data models, update `.codeyam/data-structure.json`');
|
|
277
|
+
console.log(chalk.dim(' Add new tables, update fields, or change descriptions to match.'));
|
|
278
|
+
}
|
|
279
|
+
console.log();
|
|
280
|
+
}
|
|
213
281
|
/**
|
|
214
282
|
* Shared app scenario registration instructions used by editor Step 8 and migration Steps 1/6.
|
|
215
283
|
* Prints seed-based scenarios, mock-based fallback, @ file prefix, externalApis, url requirement, and error checking.
|
|
@@ -251,7 +319,12 @@ function printAppScenarioInstructions(pageName, route) {
|
|
|
251
319
|
console.log(chalk.dim(' For external APIs: also add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
252
320
|
}
|
|
253
321
|
else {
|
|
322
|
+
const appCtx = getTechStackContext(root);
|
|
254
323
|
checkbox('Include data in every app scenario — without it the page will be empty:');
|
|
324
|
+
if (appCtx.isExpo) {
|
|
325
|
+
console.log(chalk.dim(' Use "localStorage":{"items":"[...]"} to pre-populate AsyncStorage (values are JSON strings)'));
|
|
326
|
+
console.log(chalk.dim(" AsyncStorage uses localStorage on web — CodeYam's injection works automatically."));
|
|
327
|
+
}
|
|
255
328
|
console.log(chalk.dim(' Use "mockData":{"routes":{"/api/...":{"body":[...]}}} to mock API responses'));
|
|
256
329
|
console.log(chalk.dim(' For external APIs: add "externalApis":{"GET https://...":{"body":[...],"status":200}}'));
|
|
257
330
|
}
|
|
@@ -326,9 +399,11 @@ function printExtractionPlanInstructions() {
|
|
|
326
399
|
* Shared component capture instructions used by editor Step 7 and migration Steps 3/8.
|
|
327
400
|
* Prints the full isolation route setup, codeyam-capture wrapper, register command, and error checking.
|
|
328
401
|
*/
|
|
329
|
-
function printComponentCaptureInstructions() {
|
|
402
|
+
function printComponentCaptureInstructions(root) {
|
|
403
|
+
const ctx = root ? getTechStackContext(root) : undefined;
|
|
404
|
+
const isExpo = ctx?.isExpo ?? false;
|
|
330
405
|
checkbox('Create isolation route dirs: `codeyam editor isolate ComponentA ComponentB ...`');
|
|
331
|
-
console.log(chalk.dim(
|
|
406
|
+
console.log(chalk.dim(` This creates app/isolated-components/layout.tsx (with production ${isExpo ? '__DEV__' : 'notFound()'} guard) and`));
|
|
332
407
|
console.log(chalk.dim(' a directory per component. List ALL components that need isolation routes.'));
|
|
333
408
|
checkbox('For each visual component:');
|
|
334
409
|
console.log(chalk.dim(' 1. Read the source AND find where it is used in the app to understand:'));
|
|
@@ -340,23 +415,37 @@ function printComponentCaptureInstructions() {
|
|
|
340
415
|
console.log(chalk.dim(' — Different visual states: loading, error, disabled, selected, hover'));
|
|
341
416
|
console.log(chalk.dim(' — Boundary values: single item vs many, min/max ratings, very long names'));
|
|
342
417
|
console.log(chalk.dim(' 3. Create ONE isolation route per component with a scenarios map and ?s= query param:'));
|
|
343
|
-
|
|
344
|
-
|
|
418
|
+
if (isExpo) {
|
|
419
|
+
console.log(chalk.dim(' Expo: app/isolated-components/ComponentName.tsx → /isolated-components/ComponentName'));
|
|
420
|
+
console.log(chalk.dim(' Use useLocalSearchParams() from expo-router to read ?s=ScenarioName.'));
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
console.log(chalk.dim(' Remix: app/routes/isolated-components.ComponentName.tsx → /isolated-components/ComponentName'));
|
|
424
|
+
console.log(chalk.dim(' Next.js: app/isolated-components/ComponentName/page.tsx → /isolated-components/ComponentName'));
|
|
425
|
+
}
|
|
345
426
|
console.log(chalk.dim(' The route defines a `scenarios` object mapping scenario names to props,'));
|
|
346
427
|
console.log(chalk.dim(' reads `?s=ScenarioName` from the URL, and renders the component with those props.'));
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
428
|
+
if (isExpo) {
|
|
429
|
+
console.log(chalk.dim(' Wrap the component in a capture container with nativeID="codeyam-capture":'));
|
|
430
|
+
console.log(chalk.dim(' <View nativeID="codeyam-capture" style={{ display:"flex" }}>'));
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
console.log(chalk.dim(' Wrap the component in a capture container with id="codeyam-capture":'));
|
|
434
|
+
console.log(chalk.dim(' <div id="codeyam-capture" style={{ display:"inline-block" }}>'));
|
|
435
|
+
}
|
|
436
|
+
console.log(chalk.dim(isExpo
|
|
437
|
+
? ' <View style={{ width:"100%", maxWidth:... }}> ← match the app\'s container width'
|
|
438
|
+
: ' <div style={{ width:"100%", maxWidth:"..." }}> ← match the app\'s container width'));
|
|
439
|
+
console.log(chalk.dim(' e.g. card in a 3-col grid → maxWidth: 384, full-width component → omit maxWidth'));
|
|
351
440
|
console.log(chalk.dim(' The screenshot captures just this wrapper, so the component fills the image.'));
|
|
352
441
|
console.log(chalk.dim(' Center the wrapper on the page (flexbox center both axes) and set a page background'));
|
|
353
442
|
console.log(chalk.dim(' color that matches where the component normally appears (e.g. white for light UIs).'));
|
|
354
443
|
console.log(chalk.dim(' 4. Wait 2 seconds for HMR, then register + capture each scenario:'));
|
|
355
444
|
console.log(chalk.dim(` codeyam editor register '{"name":"ComponentName - Scenario",`));
|
|
356
445
|
console.log(chalk.dim(` "componentName":"ComponentName","componentPath":"path/to/file.tsx",`));
|
|
357
|
-
console.log(chalk.dim(` "url":"/
|
|
446
|
+
console.log(chalk.dim(` "url":"/isolated-components/ComponentName?s=Scenario",`));
|
|
358
447
|
console.log(chalk.dim(` "mockData":{"routes":{"/api/...":{"body":[...]}}}}'`));
|
|
359
|
-
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/
|
|
448
|
+
console.log(chalk.yellow(' IMPORTANT: url MUST be an isolation route (/isolated-components/...).'));
|
|
360
449
|
console.log(chalk.yellow(' Do NOT use real page URLs (like /library) for component scenarios.'));
|
|
361
450
|
console.log(chalk.dim(' mockData.routes provides data for API calls the component makes internally'));
|
|
362
451
|
console.log(chalk.dim(' (omit mockData if the component has no internal API calls)'));
|
|
@@ -366,7 +455,7 @@ function printComponentCaptureInstructions() {
|
|
|
366
455
|
console.log(chalk.dim(' Isolation routes are committed to git — the layout guard blocks them in production'));
|
|
367
456
|
console.log();
|
|
368
457
|
console.log(chalk.bold(' Bulk registration: write all component scenarios to a temp file as an array:'));
|
|
369
|
-
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/
|
|
458
|
+
console.log(chalk.dim(' [{"name":"Comp - Default","componentName":"Comp","componentPath":"...","url":"/isolated-components/Comp?s=Default"}, ...]'));
|
|
370
459
|
console.log(chalk.dim(' codeyam editor register @.codeyam/tmp/scenarios.json'));
|
|
371
460
|
}
|
|
372
461
|
/**
|
|
@@ -419,7 +508,9 @@ function stopGate(current, opts) {
|
|
|
419
508
|
// Skip step 1 (no feature name yet — task starts at step 2)
|
|
420
509
|
if (current >= 2) {
|
|
421
510
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
422
|
-
console.log(chalk.cyan('
|
|
511
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
512
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
513
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
423
514
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
424
515
|
console.log();
|
|
425
516
|
let taskTitle;
|
|
@@ -683,11 +774,17 @@ function printSetup(root) {
|
|
|
683
774
|
console.log();
|
|
684
775
|
// ── Design System ────────────────────────────────────────────────
|
|
685
776
|
console.log(chalk.bold('Design System (ask FIRST):'));
|
|
686
|
-
console.log(chalk.dim(' Ask: "
|
|
777
|
+
console.log(chalk.dim(' Ask: "What visual style do you want? Pick a built-in design system or bring your own."'));
|
|
687
778
|
console.log(chalk.dim(' Use AskUserQuestion with these EXACT option labels:'));
|
|
688
|
-
console.log(
|
|
779
|
+
console.log();
|
|
780
|
+
for (const ds of DESIGN_SYSTEMS) {
|
|
781
|
+
console.log(chalk.yellow(` Option label: "${ds.name}"`) +
|
|
782
|
+
chalk.dim(` — ${ds.description}`));
|
|
783
|
+
console.log(chalk.dim(` → Run: codeyam editor design-system ${ds.id}`));
|
|
784
|
+
}
|
|
785
|
+
console.log(chalk.yellow(' Option label: "I\'ll paste my own design system"'));
|
|
689
786
|
console.log(chalk.dim(' → Wait for paste, save to .codeyam/design-system.md, confirm with brief summary'));
|
|
690
|
-
console.log(chalk.yellow(' Option
|
|
787
|
+
console.log(chalk.yellow(' Option label: "Skip — use sensible defaults"'));
|
|
691
788
|
console.log(chalk.dim(' → Skip'));
|
|
692
789
|
console.log();
|
|
693
790
|
console.log(chalk.bold('Checklist:'));
|
|
@@ -742,7 +839,7 @@ function printSetup(root) {
|
|
|
742
839
|
console.log(chalk.yellow(' ( ) Mobile') + chalk.dim(' — 375 × 667'));
|
|
743
840
|
console.log(chalk.yellow(' ( ) Custom') + chalk.dim(' — ask for width × height'));
|
|
744
841
|
console.log();
|
|
745
|
-
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app →
|
|
842
|
+
console.log(chalk.dim(' Pre-select based on app format: mobile-responsive-web-app/desktop-app → Desktop, mobile-app → iPhone 16 (393×852), chrome-extension → Custom (400×600).'));
|
|
746
843
|
console.log(chalk.dim(' If only one obvious choice, confirm it rather than asking.'));
|
|
747
844
|
console.log(chalk.dim(` Save the choice via: curl -s -X POST http://localhost:${port}/api/editor-project-info -H "Content-Type: application/json" -d '{"defaultScreenSize":{"name":"Desktop","width":1440,"height":900}}'`));
|
|
748
845
|
console.log();
|
|
@@ -951,33 +1048,65 @@ function printStep2(root, feature) {
|
|
|
951
1048
|
}
|
|
952
1049
|
console.log('Get the project ready to build.');
|
|
953
1050
|
console.log();
|
|
1051
|
+
const ctx = getTechStackContext(root);
|
|
954
1052
|
// If no project exists yet, include scaffolding instructions first
|
|
955
1053
|
if (!projectExists) {
|
|
956
1054
|
console.log(chalk.bold('Scaffold the project:'));
|
|
957
1055
|
checkbox('Run `codeyam editor template` to scaffold, install dependencies, init git, and configure CodeYam');
|
|
958
|
-
|
|
1056
|
+
if (ctx.isExpo) {
|
|
1057
|
+
console.log(chalk.dim(' This copies the Expo + React Native template, runs npm install,'));
|
|
1058
|
+
}
|
|
1059
|
+
else if (ctx.isChromeExt) {
|
|
1060
|
+
console.log(chalk.dim(' This copies the Chrome Extension + React template, runs npm install,'));
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
console.log(chalk.dim(' This copies the Next.js + Prisma 7 + SQLite template, runs npm install,'));
|
|
1064
|
+
}
|
|
959
1065
|
console.log(chalk.dim(' initializes git, runs codeyam init, and refreshes the editor — all in one command.'));
|
|
960
1066
|
console.log();
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1067
|
+
if (ctx.isExpo) {
|
|
1068
|
+
// Expo: no database, use AsyncStorage + theme
|
|
1069
|
+
checkbox('Define your data types in `lib/types.ts`');
|
|
1070
|
+
console.log(chalk.dim(" Create TypeScript interfaces for your app's data models"));
|
|
1071
|
+
console.log();
|
|
1072
|
+
checkbox('Set up initial data using the storage helper in `lib/storage.ts`');
|
|
1073
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1074
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "First item" }]);'));
|
|
1075
|
+
console.log();
|
|
1076
|
+
console.log(chalk.dim(' Read MOBILE_SETUP.md for data storage, navigation, and testing patterns.'));
|
|
1077
|
+
console.log();
|
|
1078
|
+
}
|
|
1079
|
+
else if (ctx.isChromeExt) {
|
|
1080
|
+
// Chrome Extension: use chrome.storage
|
|
1081
|
+
checkbox('Set up data storage using chrome.storage (with localStorage fallback for dev)');
|
|
1082
|
+
console.log();
|
|
1083
|
+
console.log(chalk.dim(' Read EXTENSION_SETUP.md for storage, messaging, and manifest patterns.'));
|
|
1084
|
+
console.log();
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
// Next.js: Prisma + database
|
|
1088
|
+
checkbox('Define your data models in `prisma/schema.prisma`');
|
|
1089
|
+
console.log(chalk.dim(" Replace the placeholder Todo model with your app's models"));
|
|
1090
|
+
console.log();
|
|
1091
|
+
checkbox('Push schema and seed the database');
|
|
1092
|
+
console.log(chalk.dim(' npm run db:push'));
|
|
1093
|
+
console.log(chalk.dim(' # Edit prisma/seed.ts with your seed data, then:'));
|
|
1094
|
+
console.log(chalk.dim(' npm run db:seed'));
|
|
1095
|
+
console.log(chalk.dim(` # After re-seeding, restart the dev server to pick up fresh data:`));
|
|
1096
|
+
console.log(chalk.dim(` # codeyam editor dev-server '{"action":"restart"}'`));
|
|
1097
|
+
console.log();
|
|
1098
|
+
console.log(chalk.yellow(' IMPORTANT: When adding new required columns to existing tables,'));
|
|
1099
|
+
console.log(chalk.yellow(' provide a @default(...) value so `db push` can fill existing rows.'));
|
|
1100
|
+
console.log(chalk.dim(' Example: userId String @default("anonymous") — existing rows get "anonymous"'));
|
|
1101
|
+
console.log(chalk.dim(' Without a default, Prisma requires --force-reset which drops ALL data.'));
|
|
1102
|
+
console.log(chalk.dim(' NEVER use --force-reset — it is blocked in this environment.'));
|
|
1103
|
+
console.log();
|
|
1104
|
+
console.log(chalk.dim(' See DATABASE.md for Prisma patterns and important warnings.'));
|
|
1105
|
+
console.log(chalk.dim(' Key: import { prisma } from "@/app/lib/prisma" in API routes.'));
|
|
1106
|
+
console.log(chalk.dim(' Key: Seed scripts must use the adapter pattern (see prisma/seed.ts).'));
|
|
1107
|
+
console.log();
|
|
1108
|
+
}
|
|
1109
|
+
printDataStructureInstructions();
|
|
981
1110
|
}
|
|
982
1111
|
else {
|
|
983
1112
|
console.log(chalk.bold('Prepare the database for this feature:'));
|
|
@@ -992,6 +1121,7 @@ function printStep2(root, feature) {
|
|
|
992
1121
|
console.log(chalk.dim(' This switches the active scenario, runs the seed adapter, and refreshes the preview.'));
|
|
993
1122
|
console.log(chalk.dim(' If no existing scenario fits, use the default seed: npm run db:seed'));
|
|
994
1123
|
console.log();
|
|
1124
|
+
printDataStructureInstructions();
|
|
995
1125
|
}
|
|
996
1126
|
stopGate(2);
|
|
997
1127
|
}
|
|
@@ -1017,26 +1147,42 @@ function printStep3(root, feature) {
|
|
|
1017
1147
|
if (isResuming) {
|
|
1018
1148
|
printResumptionHeader(3);
|
|
1019
1149
|
}
|
|
1150
|
+
const ctx = getTechStackContext(root);
|
|
1020
1151
|
console.log('Build fast with real data. Prioritize speed over quality.');
|
|
1021
1152
|
console.log();
|
|
1022
1153
|
console.log(chalk.bold('Checklist:'));
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1154
|
+
if (ctx.isExpo) {
|
|
1155
|
+
checkbox('Build screens that read from AsyncStorage via `lib/storage.ts` or fetch from APIs');
|
|
1156
|
+
if (!projectExists) {
|
|
1157
|
+
checkbox('Populate initial data in AsyncStorage for development');
|
|
1158
|
+
console.log(chalk.dim(' import { storage } from "@/lib/storage";'));
|
|
1159
|
+
console.log(chalk.dim(' await storage.set("items", [{ id: "1", title: "Buy groceries" }]);'));
|
|
1160
|
+
console.log();
|
|
1161
|
+
console.log(chalk.bold.cyan('Make data visually rich:'));
|
|
1162
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1163
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1164
|
+
console.log(chalk.cyan(' • Rich data makes the prototype look real and surfaces layout issues early'));
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
checkbox('Create API routes that read from the database via Prisma');
|
|
1169
|
+
if (!projectExists) {
|
|
1170
|
+
checkbox('Seed the database with demo data');
|
|
1171
|
+
checkbox('Create `.codeyam/seed-adapter.ts` so CodeYam can seed the database for scenarios');
|
|
1172
|
+
console.log(chalk.dim(' The seed adapter reads a JSON file (path passed as CLI arg), wipes tables, inserts rows.'));
|
|
1173
|
+
console.log(chalk.dim(" Use the project's own ORM (Prisma, Drizzle, etc.). See template for example."));
|
|
1174
|
+
console.log(chalk.dim(' Run with: npx tsx .codeyam/seed-adapter.ts <path-to-seed-data.json>'));
|
|
1175
|
+
console.log();
|
|
1176
|
+
console.log(chalk.bold.cyan('Make seed data visually rich:'));
|
|
1177
|
+
console.log(chalk.cyan(' • Use real placeholder images from Unsplash (https://images.unsplash.com/photo-<id>?w=400&h=300&fit=crop)'));
|
|
1178
|
+
console.log(chalk.cyan(' • Use avatar services like i.pravatar.cc for user profile photos'));
|
|
1179
|
+
console.log(chalk.cyan(' • Write realistic, varied content — not "Item 1", "Item 2", "Test Description"'));
|
|
1180
|
+
console.log(chalk.cyan(' • Include different text lengths, categories, dates, and statuses'));
|
|
1181
|
+
console.log(chalk.cyan(' • Rich seed data makes the prototype look real and surfaces layout issues early'));
|
|
1182
|
+
}
|
|
1037
1183
|
}
|
|
1038
1184
|
checkbox('Verify the dev server shows the changes');
|
|
1039
|
-
checkbox(
|
|
1185
|
+
checkbox(`If the feature involves auth, email, payments, or other common patterns: read ${ctx.patternsFile}`);
|
|
1040
1186
|
// Responsive design guidance when building a mobile-responsive web app
|
|
1041
1187
|
if (prevState?.appFormats?.includes('mobile-responsive-web-app')) {
|
|
1042
1188
|
console.log();
|
|
@@ -1053,16 +1199,29 @@ function printStep3(root, feature) {
|
|
|
1053
1199
|
console.log();
|
|
1054
1200
|
console.log(designSystem);
|
|
1055
1201
|
console.log();
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1202
|
+
if (ctx.isExpo) {
|
|
1203
|
+
checkbox('Define ALL design tokens in `lib/theme.ts` — this is the single source of truth');
|
|
1204
|
+
console.log(chalk.dim(' Colors: theme.colors.bgSurface, theme.colors.textPrimary, etc.'));
|
|
1205
|
+
console.log(chalk.dim(' Typography: theme.fontSize.sm, theme.fontSize.lg, theme.fontFamily.mono'));
|
|
1206
|
+
console.log(chalk.dim(' Spacing: theme.spacing.sm, theme.spacing.md, theme.spacing.lg, etc.'));
|
|
1207
|
+
console.log(chalk.dim(' Border radius: theme.borderRadius.sm, theme.borderRadius.lg, etc.'));
|
|
1208
|
+
checkbox('Import theme in every component — ZERO hardcoded color strings or pixel values');
|
|
1209
|
+
console.log(chalk.dim(' Bad: color: "#333", fontSize: 14, padding: 12'));
|
|
1210
|
+
console.log(chalk.dim(' Good: color: theme.colors.textPrimary, fontSize: theme.fontSize.sm, padding: theme.spacing.md'));
|
|
1211
|
+
console.log(chalk.dim(' Do NOT use CSS custom properties (var(--token)) — they do not work in React Native.'));
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
checkbox('Define ALL design tokens as CSS custom properties in globals.css — not just colors');
|
|
1215
|
+
console.log(chalk.dim(' Colors: --bg-surface, --text-primary, --accent-green-a, etc.'));
|
|
1216
|
+
console.log(chalk.dim(' Typography: --text-xs, --text-sm, --text-lg, --text-2xl (font-size values)'));
|
|
1217
|
+
console.log(chalk.dim(' Font weights: --font-weight-normal, --font-weight-medium, --font-weight-semibold'));
|
|
1218
|
+
console.log(chalk.dim(' Spacing: --spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, etc.'));
|
|
1219
|
+
console.log(chalk.dim(' Border radius, shadows, transitions — every value the design system defines.'));
|
|
1220
|
+
checkbox('Reference tokens from components — ZERO hardcoded px values for font-size, spacing, or colors');
|
|
1221
|
+
console.log(chalk.dim(' Bad: fontSize: 14, padding: "12px 16px", gap: 8'));
|
|
1222
|
+
console.log(chalk.dim(' Good: fontSize: "var(--text-sm)", padding: "var(--spacing-md) var(--spacing-lg)", gap: "var(--spacing-sm)"'));
|
|
1223
|
+
console.log(chalk.dim(' This ensures the entire app updates when the design system changes.'));
|
|
1224
|
+
}
|
|
1066
1225
|
}
|
|
1067
1226
|
console.log();
|
|
1068
1227
|
console.log(chalk.bold.cyan('Keep the preview moving:'));
|
|
@@ -1099,14 +1258,13 @@ function printStep4(root, feature) {
|
|
|
1099
1258
|
console.log('Verify everything works before presenting the prototype.');
|
|
1100
1259
|
console.log();
|
|
1101
1260
|
console.log(chalk.bold('Verify the dev server:'));
|
|
1102
|
-
console.log(chalk.dim(
|
|
1103
|
-
console.log(chalk.dim(
|
|
1104
|
-
console.log(chalk.dim(' # Check API routes: curl -s http://localhost:<dev-port>/api/your-route'));
|
|
1261
|
+
console.log(chalk.dim(' # Verify pages and API routes load:'));
|
|
1262
|
+
console.log(chalk.dim(` codeyam editor verify-routes '{"paths":["/your-page"],"apiRoutes":["/api/your-route"]}'`));
|
|
1105
1263
|
console.log();
|
|
1106
1264
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1107
1265
|
console.log(chalk.yellow(' Verify everything works before presenting the prototype to the user.'));
|
|
1108
|
-
checkbox('Verify
|
|
1109
|
-
|
|
1266
|
+
checkbox('Verify page and API routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1267
|
+
console.log(chalk.dim(' Include ALL page paths you built and ALL API routes they depend on.'));
|
|
1110
1268
|
checkbox('Check for broken images: `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
1111
1269
|
console.log(chalk.dim(' Pass ALL page paths and ALL image URLs you used in seed data / API responses.'));
|
|
1112
1270
|
console.log(chalk.dim(' Client-rendered pages need imageUrls — the HTML shell has no images to scan.'));
|
|
@@ -1117,6 +1275,14 @@ function printStep4(root, feature) {
|
|
|
1117
1275
|
console.log(chalk.dim(' The user is looking at the preview — a blank page means something is broken.'));
|
|
1118
1276
|
console.log(chalk.dim(' If any check fails, fix the issue and re-verify before proceeding.'));
|
|
1119
1277
|
console.log();
|
|
1278
|
+
const ctx4 = getTechStackContext(root);
|
|
1279
|
+
if (ctx4.isExpo) {
|
|
1280
|
+
console.log(chalk.magenta.bold(' EXPO WEB PREVIEW NOTE:'));
|
|
1281
|
+
console.log(chalk.magenta(' The preview renders via react-native-web in a browser. Some differences'));
|
|
1282
|
+
console.log(chalk.magenta(' from native devices are expected (fonts, SafeAreaView, shadows, Platform.OS).'));
|
|
1283
|
+
console.log(chalk.magenta(' The preview is for layout and data verification. Test final polish on device.'));
|
|
1284
|
+
console.log();
|
|
1285
|
+
}
|
|
1120
1286
|
console.log(chalk.bold('Update README and setup script:'));
|
|
1121
1287
|
checkbox('Update `README.md`: set the project name, write a one-line description, and list any setup prerequisites');
|
|
1122
1288
|
checkbox('Update `npm run setup` in `package.json` if setup requires extra steps (e.g. env vars, external services)');
|
|
@@ -1149,7 +1315,7 @@ function printStep5(root, feature) {
|
|
|
1149
1315
|
console.log();
|
|
1150
1316
|
console.log(chalk.bold('Before presenting — verify everything works:'));
|
|
1151
1317
|
checkbox(`Refresh the preview: \`codeyam editor preview '{"dimension":"${dim}"}'\` — check the \`preview\` field for \`healthy: false\``);
|
|
1152
|
-
checkbox('Verify
|
|
1318
|
+
checkbox('Verify routes: `codeyam editor verify-routes \'{"paths":["/"],"apiRoutes":["/api/your-route"]}\'`');
|
|
1153
1319
|
console.log();
|
|
1154
1320
|
console.log(chalk.bold.red(' Verify EVERY image loads (this is the #1 source of broken prototypes):'));
|
|
1155
1321
|
checkbox('Run `codeyam editor verify-images \'{"paths":["/"], "imageUrls":["url1","url2"]}\'`');
|
|
@@ -1229,6 +1395,7 @@ function printStep7(root, feature) {
|
|
|
1229
1395
|
if (isResuming) {
|
|
1230
1396
|
printResumptionHeader(7);
|
|
1231
1397
|
}
|
|
1398
|
+
const ctx = getTechStackContext(root);
|
|
1232
1399
|
console.log('Execute your extraction plan from step 6.');
|
|
1233
1400
|
console.log();
|
|
1234
1401
|
console.log(chalk.bold('Components:'));
|
|
@@ -1242,18 +1409,23 @@ function printStep7(root, feature) {
|
|
|
1242
1409
|
console.log(chalk.dim(' Cover: typical inputs, edge cases, empty/null inputs, error conditions'));
|
|
1243
1410
|
console.log(chalk.dim(' Aim for 3-8 test cases per function depending on complexity'));
|
|
1244
1411
|
console.log(chalk.dim(' Hooks count as functions — useDrinks, useAuth, etc. all need test files'));
|
|
1245
|
-
|
|
1412
|
+
if (ctx.isExpo) {
|
|
1413
|
+
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`');
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
checkbox('Place test files next to source: `app/lib/drinks.ts` → `app/lib/drinks.test.ts`');
|
|
1417
|
+
}
|
|
1246
1418
|
console.log(chalk.yellow(' Tests ARE the only coverage for library functions/hooks — step 9 only captures component screenshots.'));
|
|
1247
1419
|
console.log();
|
|
1248
1420
|
console.log(chalk.bold('Recursive pass:'));
|
|
1249
1421
|
checkbox('Re-read EVERY new file you just created — extract components from components, functions from functions');
|
|
1250
1422
|
checkbox('Keep going until every file is a thin shell: just imports and composition');
|
|
1251
|
-
console.log(chalk.yellow(
|
|
1423
|
+
console.log(chalk.yellow(` Check: does any file contain raw ${ctx.rawPrimitivesList}?`));
|
|
1252
1424
|
console.log(chalk.yellow(' If yes → that JSX section is a component waiting to be extracted.'));
|
|
1253
1425
|
console.log();
|
|
1254
1426
|
console.log(chalk.bold('Verify before proceeding:'));
|
|
1255
1427
|
checkbox('Run all tests and verify they pass');
|
|
1256
|
-
checkbox(
|
|
1428
|
+
checkbox(`Page files contain ONLY imports + component composition — no raw ${ctx.isExpo ? 'React Native primitives' : 'HTML tags'}`);
|
|
1257
1429
|
checkbox('Every component renders ONE thing or composes sub-components — no multi-section JSX');
|
|
1258
1430
|
checkbox(`Refresh the preview after each batch of extractions: \`codeyam editor preview '{"dimension":"${dim}"}'\``);
|
|
1259
1431
|
printDimensionGuidance(dim, dimNames);
|
|
@@ -1314,11 +1486,12 @@ function printStep9(root, feature) {
|
|
|
1314
1486
|
console.log(chalk.dim(' Reuse and improve existing scenarios where possible — update mock data'));
|
|
1315
1487
|
console.log(chalk.dim(' to reflect current changes. Add new scenarios only for genuinely new states.'));
|
|
1316
1488
|
console.log(chalk.dim(' Ensure at least one scenario clearly demonstrates what changed in this session.'));
|
|
1317
|
-
|
|
1489
|
+
const ctx9 = getTechStackContext(root);
|
|
1490
|
+
printComponentCaptureInstructions(root);
|
|
1318
1491
|
console.log();
|
|
1319
1492
|
console.log(chalk.bold('Library Functions — run tests:'));
|
|
1320
1493
|
checkbox('Run ALL test files created in step 7');
|
|
1321
|
-
console.log(chalk.dim(
|
|
1494
|
+
console.log(chalk.dim(` Example: ${ctx9.testRunCommand} app/lib/drinks.test.ts`));
|
|
1322
1495
|
checkbox('Verify every test passes');
|
|
1323
1496
|
checkbox('If any test fails, fix the source code and re-run');
|
|
1324
1497
|
console.log();
|
|
@@ -1326,7 +1499,13 @@ function printStep9(root, feature) {
|
|
|
1326
1499
|
console.log();
|
|
1327
1500
|
checkbox('Run `codeyam editor audit` to verify all components have scenarios and all functions/hooks have tests');
|
|
1328
1501
|
console.log(chalk.red.bold(' The audit is a HARD GATE — step 10 will refuse to run until the audit passes.'));
|
|
1329
|
-
console.log(chalk.dim('
|
|
1502
|
+
console.log(chalk.dim(' The audit auto-fixes incomplete entities by running targeted analysis on just the affected files.'));
|
|
1503
|
+
console.log(chalk.dim(' If the audit reports issues (including pre-existing ones), fix ALL of them — do not skip.'));
|
|
1504
|
+
console.log(chalk.dim(' Re-run `codeyam editor audit` until all checks pass. Do NOT run `codeyam editor analyze-imports`'));
|
|
1505
|
+
console.log(chalk.dim(' — the audit runs targeted analysis automatically and is much faster.'));
|
|
1506
|
+
console.log(chalk.dim(' If the audit reports "MANUAL ANALYSIS REQUIRED" for any entities,'));
|
|
1507
|
+
console.log(chalk.dim(' follow the manual analysis steps in the audit output.'));
|
|
1508
|
+
console.log(chalk.dim(' Do NOT re-run analyze-imports for those entities — it will fail again.'));
|
|
1330
1509
|
console.log();
|
|
1331
1510
|
stopGate(9);
|
|
1332
1511
|
}
|
|
@@ -1793,7 +1972,7 @@ function printMigrateStep3(root) {
|
|
|
1793
1972
|
console.log(chalk.dim(' Component scenarios use isolation routes with a codeyam-capture wrapper for tight screenshots.'));
|
|
1794
1973
|
console.log(chalk.dim(' These supplement the app scenarios from step 2 with focused component-level views.'));
|
|
1795
1974
|
console.log();
|
|
1796
|
-
printComponentCaptureInstructions();
|
|
1975
|
+
printComponentCaptureInstructions(root);
|
|
1797
1976
|
console.log();
|
|
1798
1977
|
migrationStopGate(3, pageName, pageIndex, totalPages);
|
|
1799
1978
|
}
|
|
@@ -1928,7 +2107,7 @@ function printMigrateStep8(root) {
|
|
|
1928
2107
|
console.log(chalk.bold.red('Re-register App Scenarios (REQUIRED):'));
|
|
1929
2108
|
console.log(chalk.yellow(` You MUST re-register application scenarios for "${page.route}" to get fresh screenshots.`));
|
|
1930
2109
|
checkbox('Fetch existing scenarios: `codeyam editor scenarios`');
|
|
1931
|
-
checkbox('Find all scenarios that use the real page route (not /
|
|
2110
|
+
checkbox('Find all scenarios that use the real page route (not /isolated-components/ paths)');
|
|
1932
2111
|
checkbox('Re-register each one with the SAME name to update its screenshot');
|
|
1933
2112
|
console.log(chalk.dim(` Example: codeyam editor register '{"name":"${pageName} - Full Data","type":"application","url":"${page.route}",...}'`));
|
|
1934
2113
|
checkbox('After each registration, check the response for `clientErrors`');
|
|
@@ -1936,10 +2115,10 @@ function printMigrateStep8(root) {
|
|
|
1936
2115
|
console.log();
|
|
1937
2116
|
console.log(chalk.bold('Component Scenarios:'));
|
|
1938
2117
|
console.log(chalk.dim(' For each visual component extracted in step 7, create isolation routes and register scenarios.'));
|
|
1939
|
-
printComponentCaptureInstructions();
|
|
2118
|
+
printComponentCaptureInstructions(root);
|
|
1940
2119
|
console.log();
|
|
1941
2120
|
console.log(chalk.bold('Verify:'));
|
|
1942
|
-
checkbox('Run `codeyam editor analyze-imports` to populate import graph');
|
|
2121
|
+
checkbox('Run `codeyam editor analyze-imports` to populate import graph (or `codeyam editor analyze-imports path/to/file.tsx ...` for specific files)');
|
|
1943
2122
|
checkbox('Run library function tests: `npx jest --testPathPattern="<relevant>" --maxWorkers=2`');
|
|
1944
2123
|
checkbox('Run `codeyam editor audit` to check completeness');
|
|
1945
2124
|
checkbox('Check for client errors: `codeyam editor client-errors`');
|
|
@@ -2317,10 +2496,14 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2317
2496
|
glossaryEntries = sanitizeGlossaryEntries(parsed);
|
|
2318
2497
|
}
|
|
2319
2498
|
catch {
|
|
2499
|
+
if (options.silent)
|
|
2500
|
+
return;
|
|
2320
2501
|
console.error(chalk.red('Error: Could not parse .codeyam/glossary.json.'));
|
|
2321
2502
|
process.exit(1);
|
|
2322
2503
|
}
|
|
2323
2504
|
if (glossaryEntries.length === 0) {
|
|
2505
|
+
if (options.silent)
|
|
2506
|
+
return;
|
|
2324
2507
|
console.error(chalk.red('Error: glossary.json is empty.'));
|
|
2325
2508
|
process.exit(1);
|
|
2326
2509
|
}
|
|
@@ -2362,25 +2545,137 @@ async function handleAnalyzeImports(options = {}) {
|
|
|
2362
2545
|
// Non-fatal — scenario file paths just won't be included
|
|
2363
2546
|
}
|
|
2364
2547
|
const progress = new ProgressReporter();
|
|
2365
|
-
//
|
|
2548
|
+
// Filter to only files that actually need analysis — avoids re-analyzing
|
|
2549
|
+
// ~120 files when only 2 are incomplete.
|
|
2550
|
+
let targetFilePaths = filePaths;
|
|
2551
|
+
try {
|
|
2552
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
2553
|
+
const db = getDatabase();
|
|
2554
|
+
const { requireBranchAndProject: rbp } = await import('../utils/database.js');
|
|
2555
|
+
const { project } = await rbp(JSON.parse(fs.readFileSync(path.join(root, '.codeyam', 'config.json'), 'utf8')).projectSlug);
|
|
2556
|
+
const { filterToIncompleteFilePaths } = await import('../utils/editorAudit.js');
|
|
2557
|
+
const incomplete = await filterToIncompleteFilePaths(db, project.id, filePaths);
|
|
2558
|
+
if (incomplete.length < filePaths.length && incomplete.length > 0) {
|
|
2559
|
+
if (!options.silent) {
|
|
2560
|
+
console.log(chalk.dim(`Skipping ${filePaths.length - incomplete.length} already-analyzed files, analyzing ${incomplete.length} remaining...`));
|
|
2561
|
+
}
|
|
2562
|
+
targetFilePaths = incomplete;
|
|
2563
|
+
}
|
|
2564
|
+
else if (incomplete.length === 0) {
|
|
2565
|
+
if (!options.silent) {
|
|
2566
|
+
console.log(chalk.green('All entities already have analyses — nothing to do.'));
|
|
2567
|
+
}
|
|
2568
|
+
// Still need to run the import graph + backfill below
|
|
2569
|
+
targetFilePaths = [];
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
catch {
|
|
2573
|
+
// Non-fatal — fall back to analyzing all files
|
|
2574
|
+
}
|
|
2575
|
+
// If specific file paths were requested, scope analysis to only those files.
|
|
2576
|
+
// The full filePaths set is still used for the import graph below.
|
|
2577
|
+
if (options.filePaths && options.filePaths.length > 0) {
|
|
2578
|
+
const requested = new Set(options.filePaths);
|
|
2579
|
+
targetFilePaths = targetFilePaths.filter((fp) => requested.has(fp));
|
|
2580
|
+
if (targetFilePaths.length === 0 && !options.silent) {
|
|
2581
|
+
console.log(chalk.dim('Requested file(s) already analyzed or not in glossary — skipping analysis.'));
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
// Run data-structure-only analysis for entities that need it.
|
|
2366
2585
|
// Don't pass entityNames — entities may not exist yet (fresh clone).
|
|
2367
2586
|
// The analyzer will discover and create them from file paths.
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2587
|
+
if (targetFilePaths.length > 0) {
|
|
2588
|
+
progress.start(`Running import analysis for ${targetFilePaths.length} entit${targetFilePaths.length !== 1 ? 'ies' : 'y'}...`);
|
|
2589
|
+
try {
|
|
2590
|
+
await runAnalysisForEntities({
|
|
2591
|
+
projectRoot: root,
|
|
2592
|
+
filePaths: targetFilePaths,
|
|
2593
|
+
progress,
|
|
2594
|
+
onlyDataStructure: true,
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
catch (err) {
|
|
2598
|
+
progress.fail('Analysis failed');
|
|
2599
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2600
|
+
if (options.silent) {
|
|
2601
|
+
// Internal caller — don't kill the process, let the caller handle it
|
|
2602
|
+
return;
|
|
2603
|
+
}
|
|
2604
|
+
console.error(chalk.red(`Error: ${msg}`));
|
|
2605
|
+
process.exit(1);
|
|
2606
|
+
}
|
|
2607
|
+
progress.succeed('Analysis complete');
|
|
2376
2608
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2609
|
+
// Surface analysis errors if any occurred
|
|
2610
|
+
const errorReportPath = path.join(root, '.codeyam', 'analysis-errors.txt');
|
|
2611
|
+
if (fs.existsSync(errorReportPath)) {
|
|
2612
|
+
if (!options.silent) {
|
|
2613
|
+
console.log(chalk.yellow('\nSome entity analyses failed. Full details in .codeyam/analysis-errors.txt'));
|
|
2614
|
+
console.log(chalk.yellow('Send this file to the CodeYam team for diagnosis.'));
|
|
2615
|
+
console.log(chalk.dim('Affected entities will be missing from the import graph.'));
|
|
2616
|
+
}
|
|
2617
|
+
// Write structured analysis-failures.json for the audit to detect.
|
|
2618
|
+
// Parse the error report to find which entities failed, and cross-reference
|
|
2619
|
+
// with the files we tried to analyze.
|
|
2620
|
+
try {
|
|
2621
|
+
const { readAnalysisFailures, writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2622
|
+
const errorContent = fs.readFileSync(errorReportPath, 'utf8');
|
|
2623
|
+
const existingFailures = readAnalysisFailures(root);
|
|
2624
|
+
// Parse error messages to identify failed file paths.
|
|
2625
|
+
// Error format: "Error on step <StepName> for entity <Name> (<filePath>)"
|
|
2626
|
+
// or more generic "CodeYam Error: <message>"
|
|
2627
|
+
const entityErrorRegex = /(?:Error on step \w+ for entity (\w+))|(?:entity[: ]+["']?(\w+)["']?)/gi;
|
|
2628
|
+
const failedEntityNames = new Set();
|
|
2629
|
+
let match;
|
|
2630
|
+
while ((match = entityErrorRegex.exec(errorContent)) !== null) {
|
|
2631
|
+
const name = match[1] || match[2];
|
|
2632
|
+
if (name)
|
|
2633
|
+
failedEntityNames.add(name);
|
|
2634
|
+
}
|
|
2635
|
+
// Map failed entity names back to file paths from targetFilePaths
|
|
2636
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
2637
|
+
let glossary = [];
|
|
2638
|
+
try {
|
|
2639
|
+
glossary = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
2640
|
+
}
|
|
2641
|
+
catch {
|
|
2642
|
+
// Non-fatal
|
|
2643
|
+
}
|
|
2644
|
+
// Also: any targetFilePaths that didn't produce entities are failures
|
|
2645
|
+
const now = new Date().toISOString();
|
|
2646
|
+
const updatedFailures = { ...existingFailures };
|
|
2647
|
+
if (failedEntityNames.size > 0) {
|
|
2648
|
+
for (const name of failedEntityNames) {
|
|
2649
|
+
const entry = glossary.find((e) => e.name === name);
|
|
2650
|
+
if (entry) {
|
|
2651
|
+
updatedFailures[entry.filePath] = {
|
|
2652
|
+
entityName: name,
|
|
2653
|
+
error: `Automated analysis failed — see .codeyam/analysis-errors.txt`,
|
|
2654
|
+
failedAt: now,
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
// When we can't parse specific entity names from the error, DON'T mark
|
|
2660
|
+
// all target files as failed. The error may be non-fatal (e.g., a cache
|
|
2661
|
+
// miss logged as "CodeYam Error") and blanket-marking every file as
|
|
2662
|
+
// permanently failed blocks future audits from resolving them.
|
|
2663
|
+
writeAnalysisFailures(root, updatedFailures);
|
|
2664
|
+
}
|
|
2665
|
+
catch {
|
|
2666
|
+
// Non-fatal — failure tracking is best-effort
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
else {
|
|
2670
|
+
// No errors — clear any stale failure tracking
|
|
2671
|
+
try {
|
|
2672
|
+
const { writeAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
2673
|
+
writeAnalysisFailures(root, {});
|
|
2674
|
+
}
|
|
2675
|
+
catch {
|
|
2676
|
+
// Non-fatal
|
|
2677
|
+
}
|
|
2382
2678
|
}
|
|
2383
|
-
progress.succeed('Analysis complete');
|
|
2384
2679
|
// Load entities WITH metadata from database
|
|
2385
2680
|
progress.start('Loading entity metadata...');
|
|
2386
2681
|
await initializeEnvironment();
|
|
@@ -2563,43 +2858,87 @@ async function handleDelete(scenarioId) {
|
|
|
2563
2858
|
* Creates isolation route directories and the layout guard file.
|
|
2564
2859
|
* This avoids brace-expansion permission prompts in the embedded terminal.
|
|
2565
2860
|
*/
|
|
2861
|
+
function handleDesignSystem(designSystemId) {
|
|
2862
|
+
const root = process.cwd();
|
|
2863
|
+
const ds = DESIGN_SYSTEMS.find((d) => d.id === designSystemId);
|
|
2864
|
+
if (!ds) {
|
|
2865
|
+
console.error(chalk.red(`Error: Unknown design system "${designSystemId}". Valid options: ${DESIGN_SYSTEMS.map((d) => d.id).join(', ')}`));
|
|
2866
|
+
process.exit(1);
|
|
2867
|
+
}
|
|
2868
|
+
const srcPath = path.join(__dirname, '..', '..', 'templates', 'design-systems', ds.fileName);
|
|
2869
|
+
if (!fs.existsSync(srcPath)) {
|
|
2870
|
+
console.error(chalk.red(`Error: Design system file not found: ${srcPath}`));
|
|
2871
|
+
process.exit(1);
|
|
2872
|
+
}
|
|
2873
|
+
const destDir = path.join(root, '.codeyam');
|
|
2874
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
2875
|
+
const destPath = path.join(destDir, 'design-system.md');
|
|
2876
|
+
fs.copyFileSync(srcPath, destPath);
|
|
2877
|
+
console.log(chalk.green(`Installed "${ds.name}" design system → .codeyam/design-system.md`));
|
|
2878
|
+
}
|
|
2566
2879
|
function handleIsolate(componentNames) {
|
|
2567
2880
|
const root = process.cwd();
|
|
2881
|
+
const ctx = getTechStackContext(root);
|
|
2568
2882
|
if (componentNames.length === 0) {
|
|
2569
2883
|
console.error(chalk.red('Usage: codeyam editor isolate "ComponentA ComponentB ..."'));
|
|
2570
2884
|
process.exit(1);
|
|
2571
2885
|
}
|
|
2572
2886
|
const isolateDir = path.join(root, 'app', 'isolated-components');
|
|
2573
2887
|
// Create layout.tsx with production guard if missing
|
|
2574
|
-
const layoutPath = path.join(isolateDir, 'layout.tsx');
|
|
2888
|
+
const layoutPath = path.join(isolateDir, ctx.isExpo ? '_layout.tsx' : 'layout.tsx');
|
|
2575
2889
|
if (!fs.existsSync(layoutPath)) {
|
|
2576
2890
|
fs.mkdirSync(isolateDir, { recursive: true });
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2891
|
+
if (ctx.isExpo) {
|
|
2892
|
+
fs.writeFileSync(layoutPath, [
|
|
2893
|
+
'import { Redirect, Slot } from "expo-router";',
|
|
2894
|
+
'',
|
|
2895
|
+
'export default function CaptureLayout() {',
|
|
2896
|
+
' if (!__DEV__) return <Redirect href="/" />;',
|
|
2897
|
+
' return <Slot />;',
|
|
2898
|
+
'}',
|
|
2899
|
+
'',
|
|
2900
|
+
].join('\n'), 'utf8');
|
|
2901
|
+
}
|
|
2902
|
+
else {
|
|
2903
|
+
fs.writeFileSync(layoutPath, [
|
|
2904
|
+
'import { notFound } from "next/navigation";',
|
|
2905
|
+
'',
|
|
2906
|
+
'export default function CaptureLayout({ children }: { children: React.ReactNode }) {',
|
|
2907
|
+
' if (process.env.NODE_ENV === "production") notFound();',
|
|
2908
|
+
' return <>{children}</>;',
|
|
2909
|
+
'}',
|
|
2910
|
+
'',
|
|
2911
|
+
].join('\n'), 'utf8');
|
|
2912
|
+
}
|
|
2913
|
+
console.log(chalk.green(`Created layout guard: app/isolated-components/${ctx.isExpo ? '_layout.tsx' : 'layout.tsx'}`));
|
|
2914
|
+
}
|
|
2915
|
+
// Create isolation route for each component.
|
|
2916
|
+
// Expo Router uses flat files (ComponentName.tsx), Next.js uses subdirectories (ComponentName/page.tsx).
|
|
2589
2917
|
const created = [];
|
|
2590
2918
|
const existed = [];
|
|
2591
2919
|
for (const name of componentNames) {
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2920
|
+
if (ctx.isExpo) {
|
|
2921
|
+
const filePath = path.join(isolateDir, `${name}.tsx`);
|
|
2922
|
+
if (fs.existsSync(filePath)) {
|
|
2923
|
+
existed.push(name);
|
|
2924
|
+
}
|
|
2925
|
+
else {
|
|
2926
|
+
created.push(name);
|
|
2927
|
+
}
|
|
2595
2928
|
}
|
|
2596
2929
|
else {
|
|
2597
|
-
|
|
2598
|
-
|
|
2930
|
+
const dir = path.join(isolateDir, name);
|
|
2931
|
+
if (fs.existsSync(dir)) {
|
|
2932
|
+
existed.push(name);
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2936
|
+
created.push(name);
|
|
2937
|
+
}
|
|
2599
2938
|
}
|
|
2600
2939
|
}
|
|
2601
2940
|
if (created.length > 0) {
|
|
2602
|
-
console.log(chalk.green(`Created ${created.length} isolation route
|
|
2941
|
+
console.log(chalk.green(`Created ${created.length} isolation route(s): ${created.join(', ')}`));
|
|
2603
2942
|
}
|
|
2604
2943
|
if (existed.length > 0) {
|
|
2605
2944
|
console.log(chalk.dim(`Already existed: ${existed.join(', ')}`));
|
|
@@ -2653,6 +2992,12 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2653
2992
|
parts.push(`scenarioId="${data.scenarioId}"`);
|
|
2654
2993
|
if (data.sessionsNotified != null)
|
|
2655
2994
|
parts.push(`notified=${data.sessionsNotified}`);
|
|
2995
|
+
// Surface the HTTP health check from the dev server
|
|
2996
|
+
if (data.preview) {
|
|
2997
|
+
parts.push(`healthy=${data.preview.healthy}`);
|
|
2998
|
+
if (data.preview.error)
|
|
2999
|
+
parts.push(`error="${data.preview.error}"`);
|
|
3000
|
+
}
|
|
2656
3001
|
return parts.join(' ');
|
|
2657
3002
|
}
|
|
2658
3003
|
case 'dev-server': {
|
|
@@ -2702,6 +3047,34 @@ function formatApiSubcommandResult(subcommand, data) {
|
|
|
2702
3047
|
}
|
|
2703
3048
|
return parts.join(' ');
|
|
2704
3049
|
}
|
|
3050
|
+
case 'verify-routes': {
|
|
3051
|
+
const lines = [];
|
|
3052
|
+
lines.push(chalk.bold.yellow('━━━ Route Verification ━━━'));
|
|
3053
|
+
for (const [route, info] of Object.entries(data.routes || {})) {
|
|
3054
|
+
const r = info;
|
|
3055
|
+
if (r.ok) {
|
|
3056
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}`));
|
|
3057
|
+
}
|
|
3058
|
+
else {
|
|
3059
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
for (const [route, info] of Object.entries(data.apiRoutes || {})) {
|
|
3063
|
+
const r = info;
|
|
3064
|
+
if (r.ok && r.isJSON) {
|
|
3065
|
+
lines.push(chalk.green(` ✓ ${route} — HTTP ${r.status}, valid JSON`));
|
|
3066
|
+
}
|
|
3067
|
+
else if (r.ok) {
|
|
3068
|
+
lines.push(chalk.yellow(` ⚠ ${route} — HTTP ${r.status}, not valid JSON`));
|
|
3069
|
+
}
|
|
3070
|
+
else {
|
|
3071
|
+
lines.push(chalk.red(` ✗ ${route} — ${r.error || `HTTP ${r.status}`}`));
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
lines.push('');
|
|
3075
|
+
lines.push(data.ok ? chalk.green(data.summary) : chalk.red(data.summary));
|
|
3076
|
+
return lines.join('\n');
|
|
3077
|
+
}
|
|
2705
3078
|
default:
|
|
2706
3079
|
return null; // journal-list, show/hide-results: keep full JSON
|
|
2707
3080
|
}
|
|
@@ -2736,6 +3109,8 @@ async function handleRegister(jsonArg) {
|
|
|
2736
3109
|
const isBatch = items.length > 1;
|
|
2737
3110
|
let succeeded = 0;
|
|
2738
3111
|
let failed = 0;
|
|
3112
|
+
let warnings = 0;
|
|
3113
|
+
const issues = [];
|
|
2739
3114
|
const screenshotEntries = [];
|
|
2740
3115
|
for (let i = 0; i < items.length; i++) {
|
|
2741
3116
|
const body = items[i];
|
|
@@ -2805,11 +3180,14 @@ async function handleRegister(jsonArg) {
|
|
|
2805
3180
|
}
|
|
2806
3181
|
console.log(chalk.yellow(' Fix the issue and re-register this scenario.'));
|
|
2807
3182
|
}
|
|
3183
|
+
const resultIssues = classifyRegistrationResult(res.ok, res.status, data, String(body.name || `item ${i + 1}`));
|
|
2808
3184
|
if (!res.ok) {
|
|
2809
3185
|
console.error(chalk.dim(JSON.stringify(data, null, 2)));
|
|
2810
3186
|
failed++;
|
|
2811
3187
|
}
|
|
2812
3188
|
else {
|
|
3189
|
+
if (resultIssues.length > 0)
|
|
3190
|
+
warnings++;
|
|
2813
3191
|
succeeded++;
|
|
2814
3192
|
// Collect screenshot paths for duplicate detection
|
|
2815
3193
|
if (data.screenshotCaptured &&
|
|
@@ -2822,6 +3200,10 @@ async function handleRegister(jsonArg) {
|
|
|
2822
3200
|
});
|
|
2823
3201
|
}
|
|
2824
3202
|
}
|
|
3203
|
+
// Collect issues from this registration
|
|
3204
|
+
for (const issue of resultIssues) {
|
|
3205
|
+
issues.push(`"${issue.scenarioName}": ${issue.message}`);
|
|
3206
|
+
}
|
|
2825
3207
|
}
|
|
2826
3208
|
catch (error) {
|
|
2827
3209
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -2833,7 +3215,7 @@ async function handleRegister(jsonArg) {
|
|
|
2833
3215
|
}
|
|
2834
3216
|
// Detect duplicate screenshots in the batch
|
|
2835
3217
|
if (isBatch && screenshotEntries.length > 1) {
|
|
2836
|
-
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash');
|
|
3218
|
+
const { computeScreenshotHash, findDuplicateScreenshots } = await import('../utils/screenshotHash.js');
|
|
2837
3219
|
const hashEntries = screenshotEntries
|
|
2838
3220
|
.map((e) => {
|
|
2839
3221
|
const hash = computeScreenshotHash(e.screenshotAbsPath);
|
|
@@ -2859,9 +3241,22 @@ async function handleRegister(jsonArg) {
|
|
|
2859
3241
|
}
|
|
2860
3242
|
}
|
|
2861
3243
|
if (isBatch) {
|
|
2862
|
-
console.log(
|
|
3244
|
+
console.log('');
|
|
3245
|
+
if (failed > 0 || warnings > 0) {
|
|
3246
|
+
console.log(chalk.red.bold(`ERROR: ${failed} failed, ${warnings} with warnings out of ${items.length} scenarios`));
|
|
3247
|
+
console.log('');
|
|
3248
|
+
console.log(chalk.red.bold('Issues that MUST be fixed:'));
|
|
3249
|
+
for (const issue of issues) {
|
|
3250
|
+
console.log(chalk.red(` ✗ ${issue}`));
|
|
3251
|
+
}
|
|
3252
|
+
console.log('');
|
|
3253
|
+
console.log(chalk.red('Do NOT skip these errors. Fix each issue and re-register the affected scenarios.'));
|
|
3254
|
+
}
|
|
3255
|
+
else {
|
|
3256
|
+
console.log(chalk.green(`✓ Batch complete: ${succeeded}/${items.length} succeeded with no issues`));
|
|
3257
|
+
}
|
|
2863
3258
|
}
|
|
2864
|
-
if (failed > 0) {
|
|
3259
|
+
if (failed > 0 || warnings > 0) {
|
|
2865
3260
|
process.exit(1);
|
|
2866
3261
|
}
|
|
2867
3262
|
}
|
|
@@ -3150,10 +3545,11 @@ function handleChange(feature) {
|
|
|
3150
3545
|
* Fetch the audit result from the server.
|
|
3151
3546
|
* Returns null if the server is unreachable.
|
|
3152
3547
|
*/
|
|
3153
|
-
async function fetchAuditResult() {
|
|
3548
|
+
async function fetchAuditResult(options) {
|
|
3154
3549
|
const port = getServerPort();
|
|
3550
|
+
const params = options?.skipTests ? '?skipTests=true' : '';
|
|
3155
3551
|
try {
|
|
3156
|
-
const res = await fetch(`http://localhost:${port}/api/editor-audit`);
|
|
3552
|
+
const res = await fetch(`http://localhost:${port}/api/editor-audit${params}`);
|
|
3157
3553
|
if (!res.ok)
|
|
3158
3554
|
return null;
|
|
3159
3555
|
return await res.json();
|
|
@@ -3176,17 +3572,15 @@ async function checkAuditGate() {
|
|
|
3176
3572
|
return true; // Server unreachable — don't block
|
|
3177
3573
|
if (data.summary?.allPassing === true)
|
|
3178
3574
|
return true;
|
|
3179
|
-
//
|
|
3180
|
-
//
|
|
3181
|
-
const {
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
}
|
|
3189
|
-
// Backfill entity_sha on scenarios registered before entities existed
|
|
3575
|
+
// Lightweight auto-fix: backfill entity_sha (DB-only, fast).
|
|
3576
|
+
// Never run handleAnalyzeImports here — it takes minutes on large projects.
|
|
3577
|
+
const { isOnlyIncompleteEntities, isOnlyPreExistingIncomplete } = await import('../utils/editorAudit.js');
|
|
3578
|
+
// If the only failures are pre-existing incomplete entities (not caused by
|
|
3579
|
+
// this session), don't block — the audit text already calls these "non-blocking".
|
|
3580
|
+
if (isOnlyPreExistingIncomplete(data.summary, data.incompleteEntities)) {
|
|
3581
|
+
return true;
|
|
3582
|
+
}
|
|
3583
|
+
if (isOnlyIncompleteEntities(data.summary)) {
|
|
3190
3584
|
try {
|
|
3191
3585
|
const entities = await loadEntities({});
|
|
3192
3586
|
if (entities && entities.length > 0) {
|
|
@@ -3204,8 +3598,8 @@ async function checkAuditGate() {
|
|
|
3204
3598
|
catch {
|
|
3205
3599
|
// Fall through
|
|
3206
3600
|
}
|
|
3207
|
-
// Re-check after
|
|
3208
|
-
const retry = await fetchAuditResult();
|
|
3601
|
+
// Re-check after backfill — skip re-running tests (backfill is DB-only)
|
|
3602
|
+
const retry = await fetchAuditResult({ skipTests: true });
|
|
3209
3603
|
if (retry?.summary?.allPassing === true)
|
|
3210
3604
|
return true;
|
|
3211
3605
|
}
|
|
@@ -3228,6 +3622,8 @@ function printAuditGateFailures(data) {
|
|
|
3228
3622
|
const missing = (data.components || []).filter((c) => c.status === 'missing');
|
|
3229
3623
|
for (const c of missing) {
|
|
3230
3624
|
issues.push(` → ${c.name}${c.filePath ? ` (${c.filePath})` : ''}`);
|
|
3625
|
+
if (c.hint)
|
|
3626
|
+
issues.push(` Fix: ${c.hint}`);
|
|
3231
3627
|
}
|
|
3232
3628
|
}
|
|
3233
3629
|
if (s.componentsWithErrors > 0) {
|
|
@@ -3241,7 +3637,15 @@ function printAuditGateFailures(data) {
|
|
|
3241
3637
|
issues.push(`${s.functionsMissing} function(s) missing test files`);
|
|
3242
3638
|
const missing = (data.functions || []).filter((f) => f.status === 'missing');
|
|
3243
3639
|
for (const f of missing) {
|
|
3244
|
-
|
|
3640
|
+
if (f.testFile) {
|
|
3641
|
+
issues.push(` → ${f.name} — test file missing: ${f.testFile}`);
|
|
3642
|
+
}
|
|
3643
|
+
else {
|
|
3644
|
+
issues.push(` → ${f.name} (${f.filePath}) — no testFile in glossary`);
|
|
3645
|
+
if (f.suggestedTestFile) {
|
|
3646
|
+
issues.push(` Fix: Create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to .codeyam/glossary.json`);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3245
3649
|
}
|
|
3246
3650
|
}
|
|
3247
3651
|
if (s.functionsFailing > 0) {
|
|
@@ -3256,6 +3660,10 @@ function printAuditGateFailures(data) {
|
|
|
3256
3660
|
const runnerErrors = (data.functions || []).filter((f) => f.status === 'runner_error');
|
|
3257
3661
|
for (const f of runnerErrors) {
|
|
3258
3662
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3663
|
+
if (f.hint)
|
|
3664
|
+
issues.push(` ${f.hint}`);
|
|
3665
|
+
else if (f.errorMessage)
|
|
3666
|
+
issues.push(` Error: ${f.errorMessage}`);
|
|
3259
3667
|
}
|
|
3260
3668
|
}
|
|
3261
3669
|
if (s.functionsNameMismatch > 0) {
|
|
@@ -3263,13 +3671,44 @@ function printAuditGateFailures(data) {
|
|
|
3263
3671
|
const mismatch = (data.functions || []).filter((f) => f.status === 'name_mismatch');
|
|
3264
3672
|
for (const f of mismatch) {
|
|
3265
3673
|
issues.push(` → ${f.name}${f.testFile ? ` (${f.testFile})` : ''}`);
|
|
3674
|
+
if (f.hint)
|
|
3675
|
+
issues.push(` Fix: ${f.hint}`);
|
|
3266
3676
|
}
|
|
3267
3677
|
}
|
|
3268
|
-
if (s.missingFromGlossary > 0)
|
|
3678
|
+
if (s.missingFromGlossary > 0) {
|
|
3269
3679
|
issues.push(`${s.missingFromGlossary} file(s) with scenarios not in glossary`);
|
|
3680
|
+
const missingGlossary = data.missingFromGlossary || [];
|
|
3681
|
+
for (const m of missingGlossary) {
|
|
3682
|
+
issues.push(` → ${m.name} (${m.filePath})`);
|
|
3683
|
+
}
|
|
3684
|
+
const missingPaths = missingGlossary
|
|
3685
|
+
.map((m) => m.filePath)
|
|
3686
|
+
.filter(Boolean);
|
|
3687
|
+
if (missingPaths.length > 0) {
|
|
3688
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports ${missingPaths.join(' ')}\``);
|
|
3689
|
+
}
|
|
3690
|
+
else {
|
|
3691
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to add these files to the glossary`);
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3270
3694
|
if (s.incompleteEntities > 0) {
|
|
3695
|
+
// Check for persistent analysis failures
|
|
3696
|
+
let analysisFailures = {};
|
|
3697
|
+
try {
|
|
3698
|
+
const failuresPath = path.join(getProjectRoot(), '.codeyam', 'analysis-failures.json');
|
|
3699
|
+
if (fs.existsSync(failuresPath)) {
|
|
3700
|
+
analysisFailures = JSON.parse(fs.readFileSync(failuresPath, 'utf8'));
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
catch {
|
|
3704
|
+
// Non-fatal
|
|
3705
|
+
}
|
|
3271
3706
|
const preCount = s.preExistingIncompleteEntities || 0;
|
|
3272
|
-
|
|
3707
|
+
const hasFailures = Object.keys(analysisFailures).length > 0;
|
|
3708
|
+
if (hasFailures) {
|
|
3709
|
+
issues.push(`${s.incompleteEntities} entity/entities — automated analysis failed, manual analysis required`);
|
|
3710
|
+
}
|
|
3711
|
+
else if (preCount > 0 && preCount === s.incompleteEntities) {
|
|
3273
3712
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis (pre-existing — not caused by your current changes, but must be fixed before proceeding)`);
|
|
3274
3713
|
}
|
|
3275
3714
|
else if (preCount > 0) {
|
|
@@ -3278,18 +3717,52 @@ function printAuditGateFailures(data) {
|
|
|
3278
3717
|
else {
|
|
3279
3718
|
issues.push(`${s.incompleteEntities} entity/entities need import analysis`);
|
|
3280
3719
|
}
|
|
3720
|
+
const incomplete = data.incompleteEntities || [];
|
|
3721
|
+
for (const e of incomplete) {
|
|
3722
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
3723
|
+
if (failureEntry) {
|
|
3724
|
+
issues.push(` → ${e.name} — MANUAL ANALYSIS REQUIRED (automated analysis failed)`);
|
|
3725
|
+
issues.push(` Run \`codeyam editor audit\` for step-by-step manual analysis instructions`);
|
|
3726
|
+
}
|
|
3727
|
+
else {
|
|
3728
|
+
issues.push(` → ${e.name} (${e.scenarioCount} scenario${e.scenarioCount !== 1 ? 's' : ''})${e.preExisting ? ' [pre-existing]' : ''}`);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
if (!hasFailures) {
|
|
3732
|
+
issues.push(` Fix: Run \`codeyam editor analyze-imports\` to build import graphs`);
|
|
3733
|
+
}
|
|
3281
3734
|
}
|
|
3282
3735
|
if (s.unassociatedScenarios > 0) {
|
|
3283
|
-
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3284
3736
|
const unassociated = data.unassociatedScenarios || [];
|
|
3737
|
+
const unassocPaths = unassociated
|
|
3738
|
+
.map((u) => u.filePath)
|
|
3739
|
+
.filter(Boolean);
|
|
3740
|
+
if (unassocPaths.length > 0) {
|
|
3741
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports ${unassocPaths.join(' ')}\` then re-run audit`);
|
|
3742
|
+
}
|
|
3743
|
+
else {
|
|
3744
|
+
issues.push(`${s.unassociatedScenarios} component(s) have scenarios with no entity link — run \`codeyam editor analyze-imports\` then re-run audit`);
|
|
3745
|
+
}
|
|
3285
3746
|
for (const u of unassociated) {
|
|
3286
3747
|
issues.push(` → ${u.name} (${u.filePath}) — ${u.scenarioCount} scenario${u.scenarioCount !== 1 ? 's' : ''}`);
|
|
3287
3748
|
}
|
|
3288
3749
|
}
|
|
3289
|
-
if (s.miscategorizedScenarios > 0)
|
|
3750
|
+
if (s.miscategorizedScenarios > 0) {
|
|
3290
3751
|
issues.push(`${s.miscategorizedScenarios} component(s) using page URLs instead of isolation routes`);
|
|
3291
|
-
|
|
3752
|
+
const miscategorized = data.miscategorizedScenarios || [];
|
|
3753
|
+
for (const m of miscategorized) {
|
|
3754
|
+
issues.push(` → ${m.componentName} at ${m.url} (${m.scenarioNames.length} scenario${m.scenarioNames.length !== 1 ? 's' : ''})`);
|
|
3755
|
+
}
|
|
3756
|
+
issues.push(` Fix: Re-register these as app-level scenarios (with pageFilePath, no componentName) or use isolation routes`);
|
|
3757
|
+
}
|
|
3758
|
+
if (s.scenariosNeedingRecapture > 0) {
|
|
3292
3759
|
issues.push(`${s.scenariosNeedingRecapture} scenario(s) need recapture (dependency tree changed)`);
|
|
3760
|
+
const recapture = data.scenariosNeedingRecapture || [];
|
|
3761
|
+
for (const r of recapture) {
|
|
3762
|
+
issues.push(` → "${r.scenarioName}" (entity: ${r.entityName}, ${r.status?.status || 'changed'})`);
|
|
3763
|
+
}
|
|
3764
|
+
issues.push(` Fix: Re-capture affected scenarios to update screenshots after code changes`);
|
|
3765
|
+
}
|
|
3293
3766
|
if (issues.length > 0) {
|
|
3294
3767
|
console.error(chalk.yellow('\nAudit failures:'));
|
|
3295
3768
|
for (const issue of issues) {
|
|
@@ -3324,28 +3797,22 @@ function printAuditGateFailures(data) {
|
|
|
3324
3797
|
* which glossary components have registered scenarios and which functions
|
|
3325
3798
|
* have test files. Exits with code 1 if anything is missing.
|
|
3326
3799
|
*/
|
|
3327
|
-
async function handleAudit() {
|
|
3800
|
+
async function handleAudit(options) {
|
|
3328
3801
|
let data = await fetchAuditResult();
|
|
3329
3802
|
if (!data) {
|
|
3330
3803
|
console.error(chalk.red('Error: Could not reach the CodeYam server. Is it running?'));
|
|
3331
3804
|
process.exit(1);
|
|
3332
3805
|
}
|
|
3333
|
-
//
|
|
3334
|
-
//
|
|
3335
|
-
//
|
|
3336
|
-
//
|
|
3806
|
+
// Two-phase auto-fix for entity associations:
|
|
3807
|
+
// Phase 1: DB-only backfill — fast, matches existing entities to scenarios.
|
|
3808
|
+
// Phase 2: If backfill fails, targeted analyze-imports for only the
|
|
3809
|
+
// specific unresolved files (1-3 files, not the full glossary scan).
|
|
3337
3810
|
const incompleteBeforeFix = data.incompleteEntities || [];
|
|
3338
3811
|
const unassociatedBeforeFix = data.unassociatedScenarios || [];
|
|
3339
3812
|
let autoRemediationFailed = false;
|
|
3340
3813
|
if (incompleteBeforeFix.length > 0 || unassociatedBeforeFix.length > 0) {
|
|
3341
3814
|
const issueCount = incompleteBeforeFix.length + unassociatedBeforeFix.length;
|
|
3342
|
-
console.log(chalk.dim(`
|
|
3343
|
-
try {
|
|
3344
|
-
await handleAnalyzeImports({ silent: true });
|
|
3345
|
-
}
|
|
3346
|
-
catch {
|
|
3347
|
-
// Fall through — the audit will still report them
|
|
3348
|
-
}
|
|
3815
|
+
console.log(chalk.dim(`Backfilling ${issueCount} entity association issue${issueCount !== 1 ? 's' : ''}...`));
|
|
3349
3816
|
// Backfill entity_sha on scenarios that were registered before entities existed
|
|
3350
3817
|
try {
|
|
3351
3818
|
const entities = await loadEntities({});
|
|
@@ -3364,20 +3831,65 @@ async function handleAudit() {
|
|
|
3364
3831
|
catch {
|
|
3365
3832
|
// Fall through — re-fetch will show remaining issues
|
|
3366
3833
|
}
|
|
3367
|
-
// Re-fetch audit results after the
|
|
3368
|
-
|
|
3834
|
+
// Re-fetch audit results after the backfill — skip re-running tests
|
|
3835
|
+
// since they haven't changed (backfill is DB-only).
|
|
3836
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3369
3837
|
if (!data) {
|
|
3370
|
-
console.error(chalk.red('Error: Could not reach the CodeYam server after
|
|
3838
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after backfill.'));
|
|
3371
3839
|
process.exit(1);
|
|
3372
3840
|
}
|
|
3373
|
-
// If issues persist after
|
|
3841
|
+
// If issues persist after DB-only backfill, run targeted analyze-imports
|
|
3842
|
+
// for ONLY the specific files that need entities. This is fast (1-3 files)
|
|
3843
|
+
// unlike full analyze-imports which scans all glossary entries.
|
|
3844
|
+
const incompleteAfterBackfill = data.incompleteEntities || [];
|
|
3845
|
+
const unassociatedAfterBackfill = data.unassociatedScenarios || [];
|
|
3846
|
+
if (incompleteAfterBackfill.length > 0 ||
|
|
3847
|
+
unassociatedAfterBackfill.length > 0) {
|
|
3848
|
+
const { determineTargetedAnalysisPaths } = await import('../utils/editorAudit.js');
|
|
3849
|
+
const targetPaths = determineTargetedAnalysisPaths({
|
|
3850
|
+
unassociatedScenarios: unassociatedAfterBackfill,
|
|
3851
|
+
incompleteEntities: incompleteAfterBackfill,
|
|
3852
|
+
});
|
|
3853
|
+
if (targetPaths.length > 0) {
|
|
3854
|
+
console.log(chalk.dim(`Running targeted analysis for ${targetPaths.length} file${targetPaths.length !== 1 ? 's' : ''}...`));
|
|
3855
|
+
try {
|
|
3856
|
+
await handleAnalyzeImports({
|
|
3857
|
+
silent: true,
|
|
3858
|
+
filePaths: targetPaths,
|
|
3859
|
+
});
|
|
3860
|
+
// Retry backfill after analysis created entities
|
|
3861
|
+
const entities = await loadEntities({});
|
|
3862
|
+
if (entities && entities.length > 0) {
|
|
3863
|
+
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
3864
|
+
const freshDb = getDb();
|
|
3865
|
+
await backfillEntityShaOnScenarios(freshDb, entities.map((e) => ({
|
|
3866
|
+
sha: e.sha,
|
|
3867
|
+
name: e.name,
|
|
3868
|
+
filePath: e.filePath || '',
|
|
3869
|
+
isDefaultExport: e.metadata?.notExported === false &&
|
|
3870
|
+
e.metadata?.namedExport === false,
|
|
3871
|
+
})));
|
|
3872
|
+
}
|
|
3873
|
+
// Re-fetch audit results after targeted analysis
|
|
3874
|
+
data = await fetchAuditResult({ skipTests: true });
|
|
3875
|
+
if (!data) {
|
|
3876
|
+
console.error(chalk.red('Error: Could not reach the CodeYam server after analysis.'));
|
|
3877
|
+
process.exit(1);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
catch {
|
|
3881
|
+
// Targeted analysis failed — fall through to show remaining issues
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
// Check if issues persist after all remediation attempts
|
|
3374
3886
|
const incompleteAfterFix = data.incompleteEntities || [];
|
|
3375
3887
|
const unassociatedAfterFix = data.unassociatedScenarios || [];
|
|
3376
3888
|
if (incompleteAfterFix.length > 0 || unassociatedAfterFix.length > 0) {
|
|
3377
3889
|
autoRemediationFailed = true;
|
|
3378
3890
|
}
|
|
3379
3891
|
}
|
|
3380
|
-
|
|
3892
|
+
let { components, functions, summary } = data;
|
|
3381
3893
|
console.log();
|
|
3382
3894
|
console.log(chalk.bold.cyan('━━━ Editor Audit ━━━'));
|
|
3383
3895
|
console.log();
|
|
@@ -3407,6 +3919,9 @@ async function handleAudit() {
|
|
|
3407
3919
|
}
|
|
3408
3920
|
else {
|
|
3409
3921
|
detail = chalk.red(' — no scenarios registered');
|
|
3922
|
+
if (c.hint) {
|
|
3923
|
+
detail += `\n ${chalk.yellow('Fix: ' + c.hint)}`;
|
|
3924
|
+
}
|
|
3410
3925
|
}
|
|
3411
3926
|
// Show file path for failing components always, for OK only when name is ambiguous
|
|
3412
3927
|
const isDuplicate = (componentNameCounts.get(c.name) || 0) > 1;
|
|
@@ -3450,9 +3965,16 @@ async function handleAudit() {
|
|
|
3450
3965
|
break;
|
|
3451
3966
|
case 'missing':
|
|
3452
3967
|
default:
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3968
|
+
if (f.testFile) {
|
|
3969
|
+
detail = chalk.red(` — test file missing: ${f.testFile}`);
|
|
3970
|
+
}
|
|
3971
|
+
else {
|
|
3972
|
+
detail = chalk.red(` — no test file specified in glossary`);
|
|
3973
|
+
detail += chalk.dim(` (source: ${f.filePath})`);
|
|
3974
|
+
if (f.suggestedTestFile) {
|
|
3975
|
+
detail += `\n ${chalk.yellow(`Fix: Either create ${f.suggestedTestFile} or add "testFile": "${f.suggestedTestFile}" to this entry in .codeyam/glossary.json`)}`;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3456
3978
|
break;
|
|
3457
3979
|
}
|
|
3458
3980
|
console.log(` ${icon} ${f.name}${detail}`);
|
|
@@ -3466,22 +3988,53 @@ async function handleAudit() {
|
|
|
3466
3988
|
for (const m of missingFromGlossary) {
|
|
3467
3989
|
console.log(` ${chalk.red('✗')} ${m.name} ${chalk.dim(`(${m.filePath})`)} — ${m.scenarioCount} scenario${m.scenarioCount !== 1 ? 's' : ''} but not in glossary`);
|
|
3468
3990
|
}
|
|
3469
|
-
|
|
3991
|
+
const mgPaths = missingFromGlossary
|
|
3992
|
+
.map((m) => m.filePath)
|
|
3993
|
+
.filter(Boolean);
|
|
3994
|
+
if (mgPaths.length > 0) {
|
|
3995
|
+
console.log(chalk.yellow(` Add these to .codeyam/glossary.json and run \`codeyam editor analyze-imports ${mgPaths.join(' ')}\``));
|
|
3996
|
+
}
|
|
3997
|
+
else {
|
|
3998
|
+
console.log(chalk.yellow(' Add these to .codeyam/glossary.json and run `codeyam editor analyze-imports`'));
|
|
3999
|
+
}
|
|
3470
4000
|
console.log();
|
|
3471
4001
|
}
|
|
3472
|
-
// Incomplete entities (scenarios without analyses)
|
|
4002
|
+
// Incomplete entities (scenarios without analyses) — report with guidance.
|
|
4003
|
+
// We intentionally do NOT run analysis here: it starts the analyzer
|
|
4004
|
+
// template which is slow even for a few files. Users should run
|
|
4005
|
+
// `codeyam editor analyze-imports` separately if needed.
|
|
3473
4006
|
const incompleteEntities = data.incompleteEntities || [];
|
|
3474
4007
|
if (incompleteEntities.length > 0) {
|
|
4008
|
+
const { formatIncompleteEntityGuidance, formatManualAnalysisGuidance } = await import('../utils/editorAudit.js');
|
|
4009
|
+
const { readAnalysisFailures } = await import('../utils/manualEntityAnalysis.js');
|
|
4010
|
+
// Check for persistent analysis failures
|
|
4011
|
+
const analysisFailures = readAnalysisFailures(getProjectRoot());
|
|
3475
4012
|
console.log(chalk.bold('Incomplete entities (need import analysis):'));
|
|
3476
4013
|
for (const e of incompleteEntities) {
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
4014
|
+
// Check if this entity has a persistent failure
|
|
4015
|
+
const failureEntry = Object.values(analysisFailures).find((f) => f.entityName === e.name);
|
|
4016
|
+
if (failureEntry) {
|
|
4017
|
+
// Show manual analysis instructions instead of "run analyze-imports"
|
|
4018
|
+
const filePath = Object.entries(analysisFailures).find(([, f]) => f.entityName === e.name)?.[0];
|
|
4019
|
+
const guidance = formatManualAnalysisGuidance({
|
|
4020
|
+
name: e.name,
|
|
4021
|
+
filePath: filePath || '',
|
|
4022
|
+
scenarioCount: e.scenarioCount,
|
|
4023
|
+
error: failureEntry.error,
|
|
4024
|
+
});
|
|
4025
|
+
// Print each line with proper indentation
|
|
4026
|
+
for (const line of guidance.split('\n')) {
|
|
4027
|
+
console.log(` ${chalk.red('✗')} ${line}`);
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
else {
|
|
4031
|
+
const guidance = formatIncompleteEntityGuidance(e);
|
|
4032
|
+
console.log(` ${chalk.red('✗')} ${guidance}`);
|
|
4033
|
+
}
|
|
3482
4034
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
4035
|
+
const incompleteErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4036
|
+
if (fs.existsSync(incompleteErrorReportPath)) {
|
|
4037
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3485
4038
|
}
|
|
3486
4039
|
console.log();
|
|
3487
4040
|
}
|
|
@@ -3498,12 +4051,22 @@ async function handleAudit() {
|
|
|
3498
4051
|
console.log(chalk.dim(` … and ${u.scenarioNames.length - 3} more`));
|
|
3499
4052
|
}
|
|
3500
4053
|
}
|
|
4054
|
+
const uaPaths = unassociatedScenarios
|
|
4055
|
+
.map((u) => u.filePath)
|
|
4056
|
+
.filter(Boolean);
|
|
4057
|
+
const uaCmd = uaPaths.length > 0
|
|
4058
|
+
? `codeyam editor analyze-imports ${uaPaths.join(' ')}`
|
|
4059
|
+
: 'codeyam editor analyze-imports';
|
|
3501
4060
|
if (autoRemediationFailed) {
|
|
3502
4061
|
console.log(chalk.yellow(' analyze-imports ran automatically but could not resolve these.'));
|
|
3503
|
-
console.log(chalk.yellow(
|
|
4062
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to see the full error output.`));
|
|
3504
4063
|
}
|
|
3505
4064
|
else {
|
|
3506
|
-
console.log(chalk.yellow(
|
|
4065
|
+
console.log(chalk.yellow(` Run \`${uaCmd}\` to create entity records, then re-run audit to backfill.`));
|
|
4066
|
+
}
|
|
4067
|
+
const unassocErrorReportPath = path.join(getProjectRoot(), '.codeyam', 'analysis-errors.txt');
|
|
4068
|
+
if (fs.existsSync(unassocErrorReportPath)) {
|
|
4069
|
+
console.log(chalk.dim(' See .codeyam/analysis-errors.txt for error details.'));
|
|
3507
4070
|
}
|
|
3508
4071
|
console.log();
|
|
3509
4072
|
}
|
|
@@ -3532,7 +4095,26 @@ async function handleAudit() {
|
|
|
3532
4095
|
: `${s.status.status}`;
|
|
3533
4096
|
console.log(` ${chalk.red('✗')} ${s.scenarioName} ${chalk.dim(`(${s.entityName} — ${reason})`)}`);
|
|
3534
4097
|
}
|
|
3535
|
-
|
|
4098
|
+
if (options?.fix) {
|
|
4099
|
+
// --fix: auto-recapture stale scenarios instead of just reporting them
|
|
4100
|
+
const { shouldAutoRecapture } = await import('../utils/editorAudit.js');
|
|
4101
|
+
if (shouldAutoRecapture({ fix: true, scenariosNeedingRecapture })) {
|
|
4102
|
+
console.log(chalk.cyan(' --fix: auto-recapturing stale scenarios...'));
|
|
4103
|
+
console.log();
|
|
4104
|
+
await handleRecaptureStale();
|
|
4105
|
+
// Re-fetch audit results so the summary and exit code reflect
|
|
4106
|
+
// the post-fix state, not the pre-fix state.
|
|
4107
|
+
const refreshed = await fetchAuditResult({ skipTests: true });
|
|
4108
|
+
if (refreshed) {
|
|
4109
|
+
data = refreshed;
|
|
4110
|
+
({ components, functions, summary } = data);
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
else {
|
|
4115
|
+
console.log(chalk.yellow(' Run: codeyam editor recapture-stale'));
|
|
4116
|
+
console.log(chalk.dim(' Or: codeyam editor audit --fix (to auto-recapture)'));
|
|
4117
|
+
}
|
|
3536
4118
|
console.log();
|
|
3537
4119
|
}
|
|
3538
4120
|
// Duplicate glossary names (warning, not a failure)
|
|
@@ -3595,17 +4177,6 @@ async function handleAudit() {
|
|
|
3595
4177
|
if (!allOk) {
|
|
3596
4178
|
process.exit(1);
|
|
3597
4179
|
}
|
|
3598
|
-
// Auto-run analyze-imports when audit passes — this builds the import graph
|
|
3599
|
-
// so the App tab can show component/function dependencies for each page.
|
|
3600
|
-
// Previously this was a manual step that Claude had to remember to run.
|
|
3601
|
-
console.log(chalk.dim('Building import graph...'));
|
|
3602
|
-
try {
|
|
3603
|
-
await handleAnalyzeImports({ silent: true });
|
|
3604
|
-
}
|
|
3605
|
-
catch {
|
|
3606
|
-
// Non-fatal — audit passed, import graph is a bonus
|
|
3607
|
-
console.log(chalk.yellow('Warning: Could not build import graph. Run `codeyam editor analyze-imports` manually.'));
|
|
3608
|
-
}
|
|
3609
4180
|
}
|
|
3610
4181
|
// ─── Recapture-stale subcommand ────────────────────────────────────────
|
|
3611
4182
|
/**
|
|
@@ -3812,14 +4383,14 @@ async function handleScenarioCoverage() {
|
|
|
3812
4383
|
// Safety net: heal any scenarios with null entity_sha before checking coverage
|
|
3813
4384
|
try {
|
|
3814
4385
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
3815
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
4386
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
3816
4387
|
const db = getDatabase();
|
|
3817
4388
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
3818
4389
|
if (backfillCount > 0) {
|
|
3819
4390
|
console.log(chalk.dim(` Healing ${backfillCount} scenario(s) with unlinked entities...`));
|
|
3820
4391
|
await handleAnalyzeImports({ silent: true });
|
|
3821
4392
|
// Run backfill after analysis
|
|
3822
|
-
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios');
|
|
4393
|
+
const { backfillEntityShaOnScenarios } = await import('../utils/editorScenarios.js');
|
|
3823
4394
|
const entities = await loadEntities({});
|
|
3824
4395
|
if (entities && entities.length > 0) {
|
|
3825
4396
|
await backfillEntityShaOnScenarios(db, entities.map((e) => ({
|
|
@@ -3966,7 +4537,7 @@ async function handleTemplate() {
|
|
|
3966
4537
|
_: [],
|
|
3967
4538
|
});
|
|
3968
4539
|
console.log(chalk.green(' CodeYam initialized.'));
|
|
3969
|
-
// 5. Verify config has startCommand
|
|
4540
|
+
// 5. Verify config has startCommand and set format-specific defaults
|
|
3970
4541
|
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
3971
4542
|
if (fs.existsSync(configPath)) {
|
|
3972
4543
|
try {
|
|
@@ -3977,6 +4548,26 @@ async function handleTemplate() {
|
|
|
3977
4548
|
console.log(chalk.yellow(' Warning: No startCommand found in .codeyam/config.json webapps.'));
|
|
3978
4549
|
console.log(chalk.dim(' You may need to add: "startCommand": { "command": "sh", "args": ["-c", "npm run dev -- --port $PORT"] }'));
|
|
3979
4550
|
}
|
|
4551
|
+
// Store appFormats from the tech stack so the editor UI can adapt
|
|
4552
|
+
if (stack?.supportedFormats) {
|
|
4553
|
+
config.appFormats = stack.supportedFormats;
|
|
4554
|
+
}
|
|
4555
|
+
// Set mobile-first defaults for mobile-app projects
|
|
4556
|
+
if (stack?.supportedFormats?.includes('mobile-app')) {
|
|
4557
|
+
config.defaultScreenSize = {
|
|
4558
|
+
name: 'iPhone 16',
|
|
4559
|
+
width: 393,
|
|
4560
|
+
height: 852,
|
|
4561
|
+
};
|
|
4562
|
+
config.screenSizes = {
|
|
4563
|
+
'iPhone 16': { width: 393, height: 852 },
|
|
4564
|
+
'iPhone 16 Pro Max': { width: 430, height: 932 },
|
|
4565
|
+
'iPhone SE': { width: 375, height: 667 },
|
|
4566
|
+
'Pixel 8': { width: 412, height: 915 },
|
|
4567
|
+
'iPad mini': { width: 744, height: 1133 },
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3980
4571
|
}
|
|
3981
4572
|
catch {
|
|
3982
4573
|
// Config parse error is non-fatal
|
|
@@ -4011,7 +4602,15 @@ async function handleTemplate() {
|
|
|
4011
4602
|
}
|
|
4012
4603
|
console.log();
|
|
4013
4604
|
console.log(chalk.green.bold('Project scaffolded and ready!'));
|
|
4014
|
-
|
|
4605
|
+
if (stack?.id === 'expo-react-native') {
|
|
4606
|
+
console.log(chalk.dim('Next: Set up your data types, configure the theme in lib/theme.ts, and build your feature.'));
|
|
4607
|
+
}
|
|
4608
|
+
else if (stack?.id === 'chrome-extension-react') {
|
|
4609
|
+
console.log(chalk.dim('Next: Configure your extension manifest and build your feature.'));
|
|
4610
|
+
}
|
|
4611
|
+
else {
|
|
4612
|
+
console.log(chalk.dim('Next: Define your Prisma models, push the schema, seed the database, and build your feature.'));
|
|
4613
|
+
}
|
|
4015
4614
|
}
|
|
4016
4615
|
// ─── Sync subcommand ─────────────────────────────────────────────────
|
|
4017
4616
|
/**
|
|
@@ -4316,14 +4915,205 @@ function handleEditorDebug(args) {
|
|
|
4316
4915
|
console.log(chalk.dim(' Open index.md for the full list of scenario outputs.'));
|
|
4317
4916
|
console.log();
|
|
4318
4917
|
}
|
|
4918
|
+
// ─── Manual entity analysis ───────────────────────────────────────────
|
|
4919
|
+
/**
|
|
4920
|
+
* `codeyam editor manual-entity <JSON|@file>`
|
|
4921
|
+
*
|
|
4922
|
+
* Creates entity and analysis records from Claude-provided metadata when
|
|
4923
|
+
* automated analyze-imports fails. This unblocks the audit gate without
|
|
4924
|
+
* needing the analyzer template to parse the source file.
|
|
4925
|
+
*/
|
|
4926
|
+
async function handleManualEntity(jsonArg) {
|
|
4927
|
+
const root = getProjectRoot();
|
|
4928
|
+
// Parse JSON input (supports @file.json convention)
|
|
4929
|
+
const parsed = parseRegisterArg(jsonArg);
|
|
4930
|
+
if (parsed.error || !parsed.body) {
|
|
4931
|
+
console.error(chalk.red(`Error: ${parsed.error || 'Invalid input'}`));
|
|
4932
|
+
console.error(chalk.dim(' Usage: codeyam editor manual-entity \'{"name":"Header","filePath":"src/components/Header.tsx","entityType":"visual",...}\''));
|
|
4933
|
+
console.error(chalk.dim(' Or: codeyam editor manual-entity @.codeyam/tmp/manual-entity.json'));
|
|
4934
|
+
process.exit(1);
|
|
4935
|
+
}
|
|
4936
|
+
const input = parsed.body;
|
|
4937
|
+
// Validate input
|
|
4938
|
+
const { validateManualEntityInput, convertTypeInfoToDataForMocks, readAnalysisFailures, clearFailureForPath, writeAnalysisFailures, } = await import('../utils/manualEntityAnalysis.js');
|
|
4939
|
+
// Read glossary for validation
|
|
4940
|
+
const glossaryPath = path.join(root, '.codeyam', 'glossary.json');
|
|
4941
|
+
let glossaryEntries = [];
|
|
4942
|
+
try {
|
|
4943
|
+
glossaryEntries = sanitizeGlossaryEntries(JSON.parse(fs.readFileSync(glossaryPath, 'utf8')));
|
|
4944
|
+
}
|
|
4945
|
+
catch {
|
|
4946
|
+
// Empty glossary — validation will still check file existence
|
|
4947
|
+
}
|
|
4948
|
+
const errors = validateManualEntityInput(input, glossaryEntries, {
|
|
4949
|
+
fileExists: (p) => fs.existsSync(path.join(root, p)),
|
|
4950
|
+
});
|
|
4951
|
+
if (errors.length > 0) {
|
|
4952
|
+
console.error(chalk.red('Validation errors:'));
|
|
4953
|
+
for (const err of errors) {
|
|
4954
|
+
console.error(chalk.red(` • ${err}`));
|
|
4955
|
+
}
|
|
4956
|
+
process.exit(1);
|
|
4957
|
+
}
|
|
4958
|
+
// Read source file and compute SHA
|
|
4959
|
+
const sourceContent = fs.readFileSync(path.join(root, input.filePath), 'utf8');
|
|
4960
|
+
const { generateSha } = await import('../../../packages/database/index.js');
|
|
4961
|
+
const entitySha = generateSha(input.filePath, input.name, sourceContent);
|
|
4962
|
+
// Get project and branch
|
|
4963
|
+
await initializeEnvironment();
|
|
4964
|
+
const configPath = path.join(root, '.codeyam', 'config.json');
|
|
4965
|
+
let projectSlug;
|
|
4966
|
+
try {
|
|
4967
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4968
|
+
projectSlug = config.projectSlug;
|
|
4969
|
+
}
|
|
4970
|
+
catch {
|
|
4971
|
+
console.error(chalk.red('Error: .codeyam/config.json not found or invalid.'));
|
|
4972
|
+
process.exit(1);
|
|
4973
|
+
}
|
|
4974
|
+
const { project, branch } = await requireBranchAndProject(projectSlug);
|
|
4975
|
+
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4976
|
+
const db = getDatabase();
|
|
4977
|
+
// Convert type info to dataForMocks format
|
|
4978
|
+
const dataForMocks = convertTypeInfoToDataForMocks(input.typeInfo);
|
|
4979
|
+
// Create entity record
|
|
4980
|
+
const entityMetadata = {
|
|
4981
|
+
importedExports: (input.importedExports || []).map((ie) => ({
|
|
4982
|
+
name: ie.name,
|
|
4983
|
+
filePath: ie.filePath,
|
|
4984
|
+
})),
|
|
4985
|
+
manuallyAnalyzed: true,
|
|
4986
|
+
};
|
|
4987
|
+
// Check if entity already exists
|
|
4988
|
+
const existingEntity = await db
|
|
4989
|
+
.selectFrom('entities')
|
|
4990
|
+
.select('sha')
|
|
4991
|
+
.where('sha', '=', entitySha)
|
|
4992
|
+
.executeTakeFirst();
|
|
4993
|
+
if (!existingEntity) {
|
|
4994
|
+
await db
|
|
4995
|
+
.insertInto('entities')
|
|
4996
|
+
.values({
|
|
4997
|
+
sha: entitySha,
|
|
4998
|
+
project_id: project.id,
|
|
4999
|
+
name: input.name,
|
|
5000
|
+
entity_type: input.entityType,
|
|
5001
|
+
file_path: input.filePath,
|
|
5002
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5003
|
+
})
|
|
5004
|
+
.onConflict((oc) => oc.column('sha').doNothing())
|
|
5005
|
+
.execute();
|
|
5006
|
+
console.log(chalk.dim(`Created entity record: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5007
|
+
}
|
|
5008
|
+
else {
|
|
5009
|
+
// Update metadata on existing entity
|
|
5010
|
+
await db
|
|
5011
|
+
.updateTable('entities')
|
|
5012
|
+
.set({
|
|
5013
|
+
metadata: JSON.stringify(entityMetadata),
|
|
5014
|
+
})
|
|
5015
|
+
.where('sha', '=', entitySha)
|
|
5016
|
+
.execute();
|
|
5017
|
+
console.log(chalk.dim(`Updated existing entity: ${input.name} (${entitySha.slice(0, 8)}...)`));
|
|
5018
|
+
}
|
|
5019
|
+
// Create entity_branch record
|
|
5020
|
+
await db
|
|
5021
|
+
.insertInto('entity_branches')
|
|
5022
|
+
.values({
|
|
5023
|
+
entity_sha: entitySha,
|
|
5024
|
+
branch_id: branch.id,
|
|
5025
|
+
active: true,
|
|
5026
|
+
})
|
|
5027
|
+
.onConflict((oc) => oc.columns(['entity_sha', 'branch_id']).doUpdateSet({ active: true }))
|
|
5028
|
+
.execute();
|
|
5029
|
+
// Create analysis record
|
|
5030
|
+
const { randomUUID } = await import('crypto');
|
|
5031
|
+
const analysisId = randomUUID();
|
|
5032
|
+
const now = new Date().toISOString();
|
|
5033
|
+
const analysisMetadata = {
|
|
5034
|
+
scenariosDataStructure: {
|
|
5035
|
+
dataForMocks: Object.keys(dataForMocks).length > 0 ? dataForMocks : null,
|
|
5036
|
+
},
|
|
5037
|
+
mergedDataStructure: {
|
|
5038
|
+
dependencySchemas: null,
|
|
5039
|
+
},
|
|
5040
|
+
manuallyAnalyzed: true,
|
|
5041
|
+
};
|
|
5042
|
+
const analysisStatus = {
|
|
5043
|
+
startedAt: now,
|
|
5044
|
+
finishedAt: now,
|
|
5045
|
+
steps: [],
|
|
5046
|
+
errors: [],
|
|
5047
|
+
};
|
|
5048
|
+
// Check if analysis already exists for this entity
|
|
5049
|
+
const existingAnalysis = await db
|
|
5050
|
+
.selectFrom('analyses')
|
|
5051
|
+
.select('id')
|
|
5052
|
+
.where('entity_sha', '=', entitySha)
|
|
5053
|
+
.executeTakeFirst();
|
|
5054
|
+
if (!existingAnalysis) {
|
|
5055
|
+
await db
|
|
5056
|
+
.insertInto('analyses')
|
|
5057
|
+
.values({
|
|
5058
|
+
id: analysisId,
|
|
5059
|
+
project_id: project.id,
|
|
5060
|
+
entity_sha: entitySha,
|
|
5061
|
+
entity_name: input.name,
|
|
5062
|
+
entity_type: input.entityType,
|
|
5063
|
+
file_path: input.filePath,
|
|
5064
|
+
status: JSON.stringify(analysisStatus),
|
|
5065
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5066
|
+
})
|
|
5067
|
+
.execute();
|
|
5068
|
+
console.log(chalk.dim(`Created analysis record: ${analysisId.slice(0, 8)}...`));
|
|
5069
|
+
}
|
|
5070
|
+
else {
|
|
5071
|
+
// Update existing analysis with manual metadata
|
|
5072
|
+
await db
|
|
5073
|
+
.updateTable('analyses')
|
|
5074
|
+
.set({
|
|
5075
|
+
metadata: JSON.stringify(analysisMetadata),
|
|
5076
|
+
status: JSON.stringify(analysisStatus),
|
|
5077
|
+
})
|
|
5078
|
+
.where('entity_sha', '=', entitySha)
|
|
5079
|
+
.execute();
|
|
5080
|
+
console.log(chalk.dim(`Updated existing analysis for ${input.name}`));
|
|
5081
|
+
}
|
|
5082
|
+
// Backfill entity_sha on scenarios
|
|
5083
|
+
const backfillResult = await backfillEntityShaOnScenarios(db, [
|
|
5084
|
+
{
|
|
5085
|
+
sha: entitySha,
|
|
5086
|
+
name: input.name,
|
|
5087
|
+
filePath: input.filePath,
|
|
5088
|
+
isDefaultExport: false,
|
|
5089
|
+
},
|
|
5090
|
+
]);
|
|
5091
|
+
if (backfillResult.updated > 0) {
|
|
5092
|
+
console.log(chalk.dim(`Linked ${backfillResult.updated} scenario(s) to entity`));
|
|
5093
|
+
}
|
|
5094
|
+
// Clear from analysis failures tracker
|
|
5095
|
+
const failures = readAnalysisFailures(root);
|
|
5096
|
+
if (failures[input.filePath]) {
|
|
5097
|
+
const updated = clearFailureForPath(failures, input.filePath);
|
|
5098
|
+
writeAnalysisFailures(root, updated);
|
|
5099
|
+
console.log(chalk.dim(`Cleared analysis failure for ${input.filePath}`));
|
|
5100
|
+
}
|
|
5101
|
+
console.log();
|
|
5102
|
+
console.log(chalk.green.bold(`✓ Manual entity analysis complete: ${input.name}`));
|
|
5103
|
+
console.log(chalk.dim(` Entity SHA: ${entitySha.slice(0, 12)}...`));
|
|
5104
|
+
console.log(chalk.dim(` Imports: ${(input.importedExports || []).length} glossary entities`));
|
|
5105
|
+
console.log(chalk.dim(` Type info: ${Object.keys(dataForMocks).length} fields`));
|
|
5106
|
+
console.log();
|
|
5107
|
+
console.log(chalk.dim('Re-run `codeyam editor audit` to verify.'));
|
|
5108
|
+
}
|
|
4319
5109
|
// ─── Command definition ───────────────────────────────────────────────
|
|
4320
5110
|
const editorCommand = {
|
|
4321
5111
|
command: 'editor [step] [json]',
|
|
4322
5112
|
describe: 'Editor mode guided workflow',
|
|
4323
5113
|
builder: (yargs) => {
|
|
4324
5114
|
const stepDescription = IS_INTERNAL_BUILD
|
|
4325
|
-
? 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, debug, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)'
|
|
4326
|
-
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
5115
|
+
? '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)'
|
|
5116
|
+
: 'Step number (1-18) or subcommand (template, register, isolate, analyze-imports, manual-entity, dependents, audit, scenarios, scenario-coverage, recapture-stale, change, sync, design-system, preview, show-results, hide-results, commit, journal, journal-list, journal-update, dev-server, client-errors, task-ontrack)';
|
|
4327
5117
|
let builder = yargs
|
|
4328
5118
|
.positional('step', {
|
|
4329
5119
|
type: 'string',
|
|
@@ -4354,6 +5144,11 @@ const editorCommand = {
|
|
|
4354
5144
|
alias: 'p',
|
|
4355
5145
|
describe: 'Port to run the web server on',
|
|
4356
5146
|
default: 3111,
|
|
5147
|
+
})
|
|
5148
|
+
.option('fix', {
|
|
5149
|
+
type: 'boolean',
|
|
5150
|
+
describe: 'For audit: also recapture stale scenarios after displaying results',
|
|
5151
|
+
default: false,
|
|
4357
5152
|
});
|
|
4358
5153
|
if (IS_INTERNAL_BUILD) {
|
|
4359
5154
|
builder = builder
|
|
@@ -4465,7 +5260,9 @@ const editorCommand = {
|
|
|
4465
5260
|
'Ask user what to build next and restart codeyam editor workflow';
|
|
4466
5261
|
}
|
|
4467
5262
|
console.log(chalk.bold.cyan('━━━ TASK ━━━'));
|
|
4468
|
-
console.log(chalk.cyan('
|
|
5263
|
+
console.log(chalk.cyan('Before creating the task below, delete ALL existing tasks:'));
|
|
5264
|
+
console.log(chalk.cyan(' 1. Run TaskList to find all tasks'));
|
|
5265
|
+
console.log(chalk.cyan(' 2. Run TaskUpdate with status "deleted" for EACH task returned'));
|
|
4469
5266
|
console.log(chalk.cyan('Then create a new task with this EXACT title (copy verbatim):'));
|
|
4470
5267
|
console.log();
|
|
4471
5268
|
console.log(chalk.bold.cyan(`EXACT_TASK_TITLE: ${taskTitle}`));
|
|
@@ -4479,9 +5276,16 @@ const editorCommand = {
|
|
|
4479
5276
|
fs.writeFileSync(trackingPath, JSON.stringify({ step: state.step, taskCreated: true }, null, 2), 'utf8');
|
|
4480
5277
|
return;
|
|
4481
5278
|
}
|
|
4482
|
-
// Subcommand: codeyam editor analyze-imports
|
|
5279
|
+
// Subcommand: codeyam editor analyze-imports [file1.tsx file2.tsx ...]
|
|
4483
5280
|
if (argv.step === 'analyze-imports') {
|
|
4484
|
-
await
|
|
5281
|
+
const { parseAnalyzeImportsArgs } = await import('./editorAnalyzeImportsArgs.js');
|
|
5282
|
+
const requestedPaths = parseAnalyzeImportsArgs(argv.json, argv._);
|
|
5283
|
+
await handleAnalyzeImports(requestedPaths.length > 0 ? { filePaths: requestedPaths } : {});
|
|
5284
|
+
return;
|
|
5285
|
+
}
|
|
5286
|
+
// Subcommand: codeyam editor manual-entity <JSON|@file>
|
|
5287
|
+
if (argv.step === 'manual-entity') {
|
|
5288
|
+
await handleManualEntity(argv.json || '');
|
|
4485
5289
|
return;
|
|
4486
5290
|
}
|
|
4487
5291
|
// Subcommand: codeyam editor dependents <EntityName>
|
|
@@ -4489,9 +5293,9 @@ const editorCommand = {
|
|
|
4489
5293
|
await handleDependents(argv.json || '');
|
|
4490
5294
|
return;
|
|
4491
5295
|
}
|
|
4492
|
-
// Subcommand: codeyam editor audit
|
|
5296
|
+
// Subcommand: codeyam editor audit [--fix]
|
|
4493
5297
|
if (argv.step === 'audit') {
|
|
4494
|
-
await handleAudit();
|
|
5298
|
+
await handleAudit({ fix: argv.fix || false });
|
|
4495
5299
|
return;
|
|
4496
5300
|
}
|
|
4497
5301
|
// Subcommand: codeyam editor scenarios
|
|
@@ -4524,6 +5328,11 @@ const editorCommand = {
|
|
|
4524
5328
|
await handleValidateSeed(argv.json || '');
|
|
4525
5329
|
return;
|
|
4526
5330
|
}
|
|
5331
|
+
// Subcommand: codeyam editor design-system <id>
|
|
5332
|
+
if (argv.step === 'design-system') {
|
|
5333
|
+
handleDesignSystem(argv.json || '');
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
4527
5336
|
// Subcommand: codeyam editor delete <scenarioId>
|
|
4528
5337
|
if (argv.step === 'delete') {
|
|
4529
5338
|
await handleDelete(argv.json || '');
|
|
@@ -4724,13 +5533,24 @@ const editorCommand = {
|
|
|
4724
5533
|
try {
|
|
4725
5534
|
const { getDatabase: getDb } = await import('../../../packages/database/index.js');
|
|
4726
5535
|
const seedDb = getDb();
|
|
4727
|
-
|
|
5536
|
+
// Prefer the home page scenario (url "/") since the App tab
|
|
5537
|
+
// sorts "Home" first — fall back to any application scenario.
|
|
5538
|
+
const homeScenario = await seedDb
|
|
4728
5539
|
.selectFrom('editor_scenarios')
|
|
4729
5540
|
.select(['id', 'name', 'type'])
|
|
4730
5541
|
.where('project_id', '=', project.id)
|
|
4731
|
-
.where('
|
|
5542
|
+
.where('url', '=', '/')
|
|
5543
|
+
.where('component_name', 'is', null)
|
|
4732
5544
|
.orderBy('created_at', 'asc')
|
|
4733
5545
|
.executeTakeFirst();
|
|
5546
|
+
const appScenario = homeScenario ||
|
|
5547
|
+
(await seedDb
|
|
5548
|
+
.selectFrom('editor_scenarios')
|
|
5549
|
+
.select(['id', 'name', 'type'])
|
|
5550
|
+
.where('project_id', '=', project.id)
|
|
5551
|
+
.where('type', '=', 'application')
|
|
5552
|
+
.orderBy('created_at', 'asc')
|
|
5553
|
+
.executeTakeFirst());
|
|
4734
5554
|
if (appScenario) {
|
|
4735
5555
|
const seedFile = path.join(projectRoot, '.codeyam', 'editor-scenarios', `${appScenario.id}.seed.json`);
|
|
4736
5556
|
if (fs.existsSync(seedFile)) {
|
|
@@ -4873,7 +5693,7 @@ const editorCommand = {
|
|
|
4873
5693
|
if (!needsAnalysis) {
|
|
4874
5694
|
try {
|
|
4875
5695
|
const { getDatabase } = await import('../../../packages/database/index.js');
|
|
4876
|
-
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios');
|
|
5696
|
+
const { countScenariosNeedingEntityBackfill } = await import('../utils/editorScenarios.js');
|
|
4877
5697
|
const db = getDatabase();
|
|
4878
5698
|
const backfillCount = await countScenariosNeedingEntityBackfill(db);
|
|
4879
5699
|
if (backfillCount > 0) {
|
|
@@ -4912,7 +5732,7 @@ const editorCommand = {
|
|
|
4912
5732
|
.execute();
|
|
4913
5733
|
if (unresolved.length > 0) {
|
|
4914
5734
|
const { scanPageFilePaths: scanPfp } = await import('../utils/entityChangeStatus.server.js');
|
|
4915
|
-
const { matchUrlToPageFile } = await import('../utils/routePatternMatching');
|
|
5735
|
+
const { matchUrlToPageFile } = await import('../utils/routePatternMatching.js');
|
|
4916
5736
|
const { allFiles: pfpFiles } = scanPfp(projectRoot);
|
|
4917
5737
|
let pfpResolved = 0;
|
|
4918
5738
|
for (const row of unresolved) {
|