@nac3/forge-cli 0.2.0-alpha.1
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/LICENSE +45 -0
- package/README.md +371 -0
- package/dist/bin/yf.d.ts +5 -0
- package/dist/bin/yf.d.ts.map +1 -0
- package/dist/bin/yf.js +86 -0
- package/dist/bin/yf.js.map +1 -0
- package/dist/chat/claude.d.ts +100 -0
- package/dist/chat/claude.d.ts.map +1 -0
- package/dist/chat/claude.js +228 -0
- package/dist/chat/claude.js.map +1 -0
- package/dist/chat/ingest_session.d.ts +97 -0
- package/dist/chat/ingest_session.d.ts.map +1 -0
- package/dist/chat/ingest_session.js +99 -0
- package/dist/chat/ingest_session.js.map +1 -0
- package/dist/chat/panel.d.ts +15 -0
- package/dist/chat/panel.d.ts.map +1 -0
- package/dist/chat/panel.js +1526 -0
- package/dist/chat/panel.js.map +1 -0
- package/dist/chat/persistence.d.ts +37 -0
- package/dist/chat/persistence.d.ts.map +1 -0
- package/dist/chat/persistence.js +91 -0
- package/dist/chat/persistence.js.map +1 -0
- package/dist/chat/server.d.ts +34 -0
- package/dist/chat/server.d.ts.map +1 -0
- package/dist/chat/server.js +1540 -0
- package/dist/chat/server.js.map +1 -0
- package/dist/chat/spec_extract.d.ts +35 -0
- package/dist/chat/spec_extract.d.ts.map +1 -0
- package/dist/chat/spec_extract.js +152 -0
- package/dist/chat/spec_extract.js.map +1 -0
- package/dist/chat/spec_plan.d.ts +65 -0
- package/dist/chat/spec_plan.d.ts.map +1 -0
- package/dist/chat/spec_plan.js +160 -0
- package/dist/chat/spec_plan.js.map +1 -0
- package/dist/chat/spec_scaffold.d.ts +95 -0
- package/dist/chat/spec_scaffold.d.ts.map +1 -0
- package/dist/chat/spec_scaffold.js +220 -0
- package/dist/chat/spec_scaffold.js.map +1 -0
- package/dist/chat/tools/git.d.ts +59 -0
- package/dist/chat/tools/git.d.ts.map +1 -0
- package/dist/chat/tools/git.js +313 -0
- package/dist/chat/tools/git.js.map +1 -0
- package/dist/chat/tools/github.d.ts +59 -0
- package/dist/chat/tools/github.d.ts.map +1 -0
- package/dist/chat/tools/github.js +310 -0
- package/dist/chat/tools/github.js.map +1 -0
- package/dist/chat/tools/lifecycle.d.ts +82 -0
- package/dist/chat/tools/lifecycle.d.ts.map +1 -0
- package/dist/chat/tools/lifecycle.js +295 -0
- package/dist/chat/tools/lifecycle.js.map +1 -0
- package/dist/chat/tools/manual.d.ts +26 -0
- package/dist/chat/tools/manual.d.ts.map +1 -0
- package/dist/chat/tools/manual.js +164 -0
- package/dist/chat/tools/manual.js.map +1 -0
- package/dist/chat/tools/reader.d.ts +80 -0
- package/dist/chat/tools/reader.d.ts.map +1 -0
- package/dist/chat/tools/reader.js +471 -0
- package/dist/chat/tools/reader.js.map +1 -0
- package/dist/chat/tools.d.ts +106 -0
- package/dist/chat/tools.d.ts.map +1 -0
- package/dist/chat/tools.js +587 -0
- package/dist/chat/tools.js.map +1 -0
- package/dist/codegen/e2e.d.ts +106 -0
- package/dist/codegen/e2e.d.ts.map +1 -0
- package/dist/codegen/e2e.js +931 -0
- package/dist/codegen/e2e.js.map +1 -0
- package/dist/codegen/v3_flow_emit.d.ts +70 -0
- package/dist/codegen/v3_flow_emit.d.ts.map +1 -0
- package/dist/codegen/v3_flow_emit.js +225 -0
- package/dist/codegen/v3_flow_emit.js.map +1 -0
- package/dist/commands/_stub.d.ts +2 -0
- package/dist/commands/_stub.d.ts.map +1 -0
- package/dist/commands/_stub.js +21 -0
- package/dist/commands/_stub.js.map +1 -0
- package/dist/commands/app.d.ts +31 -0
- package/dist/commands/app.d.ts.map +1 -0
- package/dist/commands/app.js +331 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/chat.d.ts +18 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +76 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/deploy.d.ts +21 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +121 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/doctor.d.ts +14 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +280 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/figma.d.ts +32 -0
- package/dist/commands/figma.d.ts.map +1 -0
- package/dist/commands/figma.js +141 -0
- package/dist/commands/figma.js.map +1 -0
- package/dist/commands/gen-flow-tests.d.ts +8 -0
- package/dist/commands/gen-flow-tests.d.ts.map +1 -0
- package/dist/commands/gen-flow-tests.js +78 -0
- package/dist/commands/gen-flow-tests.js.map +1 -0
- package/dist/commands/gen-tests.d.ts +9 -0
- package/dist/commands/gen-tests.d.ts.map +1 -0
- package/dist/commands/gen-tests.js +118 -0
- package/dist/commands/gen-tests.js.map +1 -0
- package/dist/commands/license.d.ts +14 -0
- package/dist/commands/license.d.ts.map +1 -0
- package/dist/commands/license.js +182 -0
- package/dist/commands/license.js.map +1 -0
- package/dist/commands/log.d.ts +19 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +101 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/migrate.d.ts +118 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +1410 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/mobile.d.ts +27 -0
- package/dist/commands/mobile.d.ts.map +1 -0
- package/dist/commands/mobile.js +90 -0
- package/dist/commands/mobile.js.map +1 -0
- package/dist/commands/new.d.ts +32 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +107 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pilot.d.ts +8 -0
- package/dist/commands/pilot.d.ts.map +1 -0
- package/dist/commands/pilot.js +104 -0
- package/dist/commands/pilot.js.map +1 -0
- package/dist/commands/projects.d.ts +21 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +238 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/publish.d.ts +35 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +194 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/repo.d.ts +59 -0
- package/dist/commands/repo.d.ts.map +1 -0
- package/dist/commands/repo.js +178 -0
- package/dist/commands/repo.js.map +1 -0
- package/dist/commands/review-screens.d.ts +28 -0
- package/dist/commands/review-screens.d.ts.map +1 -0
- package/dist/commands/review-screens.js +345 -0
- package/dist/commands/review-screens.js.map +1 -0
- package/dist/commands/scenarios.d.ts +23 -0
- package/dist/commands/scenarios.d.ts.map +1 -0
- package/dist/commands/scenarios.js +304 -0
- package/dist/commands/scenarios.js.map +1 -0
- package/dist/commands/ship.d.ts +18 -0
- package/dist/commands/ship.d.ts.map +1 -0
- package/dist/commands/ship.js +41 -0
- package/dist/commands/ship.js.map +1 -0
- package/dist/commands/test.d.ts +29 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +62 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/tunnel.d.ts +22 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +77 -0
- package/dist/commands/tunnel.js.map +1 -0
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +51 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/commands/vault.d.ts +32 -0
- package/dist/commands/vault.d.ts.map +1 -0
- package/dist/commands/vault.js +489 -0
- package/dist/commands/vault.js.map +1 -0
- package/dist/commands/voice.d.ts +16 -0
- package/dist/commands/voice.d.ts.map +1 -0
- package/dist/commands/voice.js +69 -0
- package/dist/commands/voice.js.map +1 -0
- package/dist/core/cascade_router.d.ts +90 -0
- package/dist/core/cascade_router.d.ts.map +1 -0
- package/dist/core/cascade_router.js +131 -0
- package/dist/core/cascade_router.js.map +1 -0
- package/dist/core/cf_tunnel.d.ts +52 -0
- package/dist/core/cf_tunnel.d.ts.map +1 -0
- package/dist/core/cf_tunnel.js +134 -0
- package/dist/core/cf_tunnel.js.map +1 -0
- package/dist/core/gha_dispatcher.d.ts +48 -0
- package/dist/core/gha_dispatcher.d.ts.map +1 -0
- package/dist/core/gha_dispatcher.js +198 -0
- package/dist/core/gha_dispatcher.js.map +1 -0
- package/dist/core/logger.d.ts +89 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +245 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/mode.d.ts +26 -0
- package/dist/core/mode.d.ts.map +1 -0
- package/dist/core/mode.js +122 -0
- package/dist/core/mode.js.map +1 -0
- package/dist/core/pairing.d.ts +40 -0
- package/dist/core/pairing.d.ts.map +1 -0
- package/dist/core/pairing.js +145 -0
- package/dist/core/pairing.js.map +1 -0
- package/dist/core/pilot_setup.d.ts +29 -0
- package/dist/core/pilot_setup.d.ts.map +1 -0
- package/dist/core/pilot_setup.js +119 -0
- package/dist/core/pilot_setup.js.map +1 -0
- package/dist/core/polar.d.ts +81 -0
- package/dist/core/polar.d.ts.map +1 -0
- package/dist/core/polar.js +175 -0
- package/dist/core/polar.js.map +1 -0
- package/dist/core/project_picker.d.ts +56 -0
- package/dist/core/project_picker.d.ts.map +1 -0
- package/dist/core/project_picker.js +86 -0
- package/dist/core/project_picker.js.map +1 -0
- package/dist/core/projects.d.ts +58 -0
- package/dist/core/projects.d.ts.map +1 -0
- package/dist/core/projects.js +146 -0
- package/dist/core/projects.js.map +1 -0
- package/dist/core/projects_sync.d.ts +80 -0
- package/dist/core/projects_sync.d.ts.map +1 -0
- package/dist/core/projects_sync.js +278 -0
- package/dist/core/projects_sync.js.map +1 -0
- package/dist/core/remote_runner.d.ts +70 -0
- package/dist/core/remote_runner.d.ts.map +1 -0
- package/dist/core/remote_runner.js +133 -0
- package/dist/core/remote_runner.js.map +1 -0
- package/dist/core/repo_state.d.ts +24 -0
- package/dist/core/repo_state.d.ts.map +1 -0
- package/dist/core/repo_state.js +109 -0
- package/dist/core/repo_state.js.map +1 -0
- package/dist/core/target.d.ts +31 -0
- package/dist/core/target.d.ts.map +1 -0
- package/dist/core/target.js +121 -0
- package/dist/core/target.js.map +1 -0
- package/dist/deploy/aws.d.ts +43 -0
- package/dist/deploy/aws.d.ts.map +1 -0
- package/dist/deploy/aws.js +173 -0
- package/dist/deploy/aws.js.map +1 -0
- package/dist/figma/api.d.ts +35 -0
- package/dist/figma/api.d.ts.map +1 -0
- package/dist/figma/api.js +40 -0
- package/dist/figma/api.js.map +1 -0
- package/dist/figma/decorator.d.ts +74 -0
- package/dist/figma/decorator.d.ts.map +1 -0
- package/dist/figma/decorator.js +210 -0
- package/dist/figma/decorator.js.map +1 -0
- package/dist/figma/heuristics.d.ts +29 -0
- package/dist/figma/heuristics.d.ts.map +1 -0
- package/dist/figma/heuristics.js +110 -0
- package/dist/figma/heuristics.js.map +1 -0
- package/dist/figma/normalize.d.ts +33 -0
- package/dist/figma/normalize.d.ts.map +1 -0
- package/dist/figma/normalize.js +101 -0
- package/dist/figma/normalize.js.map +1 -0
- package/dist/figma/tokens.d.ts +23 -0
- package/dist/figma/tokens.d.ts.map +1 -0
- package/dist/figma/tokens.js +111 -0
- package/dist/figma/tokens.js.map +1 -0
- package/dist/figma/types.d.ts +118 -0
- package/dist/figma/types.d.ts.map +1 -0
- package/dist/figma/types.js +12 -0
- package/dist/figma/types.js.map +1 -0
- package/dist/i18n/index.d.ts +48 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +135 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/types.d.ts +52 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +85 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/lan/mdns_packet.d.ts +74 -0
- package/dist/lan/mdns_packet.d.ts.map +1 -0
- package/dist/lan/mdns_packet.js +247 -0
- package/dist/lan/mdns_packet.js.map +1 -0
- package/dist/lan/mdns_service.d.ts +102 -0
- package/dist/lan/mdns_service.d.ts.map +1 -0
- package/dist/lan/mdns_service.js +206 -0
- package/dist/lan/mdns_service.js.map +1 -0
- package/dist/license/activate.d.ts +33 -0
- package/dist/license/activate.d.ts.map +1 -0
- package/dist/license/activate.js +135 -0
- package/dist/license/activate.js.map +1 -0
- package/dist/license/fingerprint.d.ts +2 -0
- package/dist/license/fingerprint.d.ts.map +1 -0
- package/dist/license/fingerprint.js +29 -0
- package/dist/license/fingerprint.js.map +1 -0
- package/dist/license/hito4_client.d.ts +24 -0
- package/dist/license/hito4_client.d.ts.map +1 -0
- package/dist/license/hito4_client.js +103 -0
- package/dist/license/hito4_client.js.map +1 -0
- package/dist/license/index.d.ts +22 -0
- package/dist/license/index.d.ts.map +1 -0
- package/dist/license/index.js +125 -0
- package/dist/license/index.js.map +1 -0
- package/dist/license/types.d.ts +38 -0
- package/dist/license/types.d.ts.map +1 -0
- package/dist/license/types.js +9 -0
- package/dist/license/types.js.map +1 -0
- package/dist/migrate/ai-apply.d.ts +198 -0
- package/dist/migrate/ai-apply.d.ts.map +1 -0
- package/dist/migrate/ai-apply.js +833 -0
- package/dist/migrate/ai-apply.js.map +1 -0
- package/dist/migrate/ai-decorator.d.ts +87 -0
- package/dist/migrate/ai-decorator.d.ts.map +1 -0
- package/dist/migrate/ai-decorator.js +203 -0
- package/dist/migrate/ai-decorator.js.map +1 -0
- package/dist/migrate/apply.d.ts +28 -0
- package/dist/migrate/apply.d.ts.map +1 -0
- package/dist/migrate/apply.js +119 -0
- package/dist/migrate/apply.js.map +1 -0
- package/dist/migrate/audit.d.ts +9 -0
- package/dist/migrate/audit.d.ts.map +1 -0
- package/dist/migrate/audit.js +197 -0
- package/dist/migrate/audit.js.map +1 -0
- package/dist/migrate/diff.d.ts +28 -0
- package/dist/migrate/diff.d.ts.map +1 -0
- package/dist/migrate/diff.js +154 -0
- package/dist/migrate/diff.js.map +1 -0
- package/dist/migrate/html-orchestrator.d.ts +81 -0
- package/dist/migrate/html-orchestrator.d.ts.map +1 -0
- package/dist/migrate/html-orchestrator.js +233 -0
- package/dist/migrate/html-orchestrator.js.map +1 -0
- package/dist/migrate/html-walker.d.ts +93 -0
- package/dist/migrate/html-walker.d.ts.map +1 -0
- package/dist/migrate/html-walker.js +288 -0
- package/dist/migrate/html-walker.js.map +1 -0
- package/dist/migrate/js-template-walker.d.ts +118 -0
- package/dist/migrate/js-template-walker.d.ts.map +1 -0
- package/dist/migrate/js-template-walker.js +644 -0
- package/dist/migrate/js-template-walker.js.map +1 -0
- package/dist/migrate/manifest-validator.d.ts +30 -0
- package/dist/migrate/manifest-validator.d.ts.map +1 -0
- package/dist/migrate/manifest-validator.js +261 -0
- package/dist/migrate/manifest-validator.js.map +1 -0
- package/dist/migrate/overrides.d.ts +58 -0
- package/dist/migrate/overrides.d.ts.map +1 -0
- package/dist/migrate/overrides.js +193 -0
- package/dist/migrate/overrides.js.map +1 -0
- package/dist/migrate/plugin-scope.d.ts +42 -0
- package/dist/migrate/plugin-scope.d.ts.map +1 -0
- package/dist/migrate/plugin-scope.js +94 -0
- package/dist/migrate/plugin-scope.js.map +1 -0
- package/dist/migrate/types.d.ts +45 -0
- package/dist/migrate/types.d.ts.map +1 -0
- package/dist/migrate/types.js +9 -0
- package/dist/migrate/types.js.map +1 -0
- package/dist/migrate/verb-inference.d.ts +37 -0
- package/dist/migrate/verb-inference.d.ts.map +1 -0
- package/dist/migrate/verb-inference.js +274 -0
- package/dist/migrate/verb-inference.js.map +1 -0
- package/dist/nac3/attrs.d.ts +87 -0
- package/dist/nac3/attrs.d.ts.map +1 -0
- package/dist/nac3/attrs.js +134 -0
- package/dist/nac3/attrs.js.map +1 -0
- package/dist/nac3/scenario_dsl.d.ts +71 -0
- package/dist/nac3/scenario_dsl.d.ts.map +1 -0
- package/dist/nac3/scenario_dsl.js +191 -0
- package/dist/nac3/scenario_dsl.js.map +1 -0
- package/dist/nac3/tokens.d.ts +126 -0
- package/dist/nac3/tokens.d.ts.map +1 -0
- package/dist/nac3/tokens.js +138 -0
- package/dist/nac3/tokens.js.map +1 -0
- package/dist/reader/parsers/csv.d.ts +42 -0
- package/dist/reader/parsers/csv.d.ts.map +1 -0
- package/dist/reader/parsers/csv.js +221 -0
- package/dist/reader/parsers/csv.js.map +1 -0
- package/dist/reader/parsers/docx.d.ts +31 -0
- package/dist/reader/parsers/docx.d.ts.map +1 -0
- package/dist/reader/parsers/docx.js +51 -0
- package/dist/reader/parsers/docx.js.map +1 -0
- package/dist/reader/parsers/epub.d.ts +39 -0
- package/dist/reader/parsers/epub.d.ts.map +1 -0
- package/dist/reader/parsers/epub.js +265 -0
- package/dist/reader/parsers/epub.js.map +1 -0
- package/dist/reader/parsers/html.d.ts +40 -0
- package/dist/reader/parsers/html.d.ts.map +1 -0
- package/dist/reader/parsers/html.js +386 -0
- package/dist/reader/parsers/html.js.map +1 -0
- package/dist/reader/parsers/md.d.ts +30 -0
- package/dist/reader/parsers/md.d.ts.map +1 -0
- package/dist/reader/parsers/md.js +199 -0
- package/dist/reader/parsers/md.js.map +1 -0
- package/dist/reader/parsers/pdf.d.ts +39 -0
- package/dist/reader/parsers/pdf.d.ts.map +1 -0
- package/dist/reader/parsers/pdf.js +220 -0
- package/dist/reader/parsers/pdf.js.map +1 -0
- package/dist/reader/parsers/rtf.d.ts +37 -0
- package/dist/reader/parsers/rtf.d.ts.map +1 -0
- package/dist/reader/parsers/rtf.js +347 -0
- package/dist/reader/parsers/rtf.js.map +1 -0
- package/dist/reader/parsers/source.d.ts +32 -0
- package/dist/reader/parsers/source.d.ts.map +1 -0
- package/dist/reader/parsers/source.js +122 -0
- package/dist/reader/parsers/source.js.map +1 -0
- package/dist/reader/parsers/txt.d.ts +25 -0
- package/dist/reader/parsers/txt.d.ts.map +1 -0
- package/dist/reader/parsers/txt.js +56 -0
- package/dist/reader/parsers/txt.js.map +1 -0
- package/dist/reader/parsers/xlsx.d.ts +33 -0
- package/dist/reader/parsers/xlsx.d.ts.map +1 -0
- package/dist/reader/parsers/xlsx.js +143 -0
- package/dist/reader/parsers/xlsx.js.map +1 -0
- package/dist/reader/registry.d.ts +39 -0
- package/dist/reader/registry.d.ts.map +1 -0
- package/dist/reader/registry.js +172 -0
- package/dist/reader/registry.js.map +1 -0
- package/dist/reader/search.d.ts +27 -0
- package/dist/reader/search.d.ts.map +1 -0
- package/dist/reader/search.js +77 -0
- package/dist/reader/search.js.map +1 -0
- package/dist/reader/state.d.ts +56 -0
- package/dist/reader/state.d.ts.map +1 -0
- package/dist/reader/state.js +179 -0
- package/dist/reader/state.js.map +1 -0
- package/dist/reader/types.d.ts +119 -0
- package/dist/reader/types.d.ts.map +1 -0
- package/dist/reader/types.js +23 -0
- package/dist/reader/types.js.map +1 -0
- package/dist/ship/run.d.ts +26 -0
- package/dist/ship/run.d.ts.map +1 -0
- package/dist/ship/run.js +123 -0
- package/dist/ship/run.js.map +1 -0
- package/dist/template/index.d.ts +50 -0
- package/dist/template/index.d.ts.map +1 -0
- package/dist/template/index.js +140 -0
- package/dist/template/index.js.map +1 -0
- package/dist/ui/colors.d.ts +13 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +26 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/validate/index.d.ts +19 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +181 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/vault/catalog.d.ts +55 -0
- package/dist/vault/catalog.d.ts.map +1 -0
- package/dist/vault/catalog.js +424 -0
- package/dist/vault/catalog.js.map +1 -0
- package/dist/vault/crypto.d.ts +82 -0
- package/dist/vault/crypto.d.ts.map +1 -0
- package/dist/vault/crypto.js +173 -0
- package/dist/vault/crypto.js.map +1 -0
- package/dist/vault/git_askpass.d.ts +26 -0
- package/dist/vault/git_askpass.d.ts.map +1 -0
- package/dist/vault/git_askpass.js +104 -0
- package/dist/vault/git_askpass.js.map +1 -0
- package/dist/vault/migrator.d.ts +57 -0
- package/dist/vault/migrator.d.ts.map +1 -0
- package/dist/vault/migrator.js +204 -0
- package/dist/vault/migrator.js.map +1 -0
- package/dist/vault/redactor.d.ts +73 -0
- package/dist/vault/redactor.d.ts.map +1 -0
- package/dist/vault/redactor.js +182 -0
- package/dist/vault/redactor.js.map +1 -0
- package/dist/vault/store.d.ts +132 -0
- package/dist/vault/store.d.ts.map +1 -0
- package/dist/vault/store.js +335 -0
- package/dist/vault/store.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/dist/voice/chunker.d.ts +43 -0
- package/dist/voice/chunker.d.ts.map +1 -0
- package/dist/voice/chunker.js +133 -0
- package/dist/voice/chunker.js.map +1 -0
- package/dist/voice/config.d.ts +14 -0
- package/dist/voice/config.d.ts.map +1 -0
- package/dist/voice/config.js +51 -0
- package/dist/voice/config.js.map +1 -0
- package/dist/voice/intents.d.ts +71 -0
- package/dist/voice/intents.d.ts.map +1 -0
- package/dist/voice/intents.js +0 -0
- package/dist/voice/intents.js.map +1 -0
- package/dist/voice/providers/elevenlabs.d.ts +53 -0
- package/dist/voice/providers/elevenlabs.d.ts.map +1 -0
- package/dist/voice/providers/elevenlabs.js +159 -0
- package/dist/voice/providers/elevenlabs.js.map +1 -0
- package/dist/voice/providers/google.d.ts +56 -0
- package/dist/voice/providers/google.d.ts.map +1 -0
- package/dist/voice/providers/google.js +253 -0
- package/dist/voice/providers/google.js.map +1 -0
- package/dist/voice/providers/whisper.d.ts +44 -0
- package/dist/voice/providers/whisper.d.ts.map +1 -0
- package/dist/voice/providers/whisper.js +179 -0
- package/dist/voice/providers/whisper.js.map +1 -0
- package/dist/voice/registry.d.ts +35 -0
- package/dist/voice/registry.d.ts.map +1 -0
- package/dist/voice/registry.js +48 -0
- package/dist/voice/registry.js.map +1 -0
- package/dist/voice/router.d.ts +62 -0
- package/dist/voice/router.d.ts.map +1 -0
- package/dist/voice/router.js +175 -0
- package/dist/voice/router.js.map +1 -0
- package/dist/voice/types.d.ts +116 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +22 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/voiceprint/enrollment.d.ts +36 -0
- package/dist/voice/voiceprint/enrollment.d.ts.map +1 -0
- package/dist/voice/voiceprint/enrollment.js +71 -0
- package/dist/voice/voiceprint/enrollment.js.map +1 -0
- package/dist/voice/voiceprint/identify.d.ts +16 -0
- package/dist/voice/voiceprint/identify.d.ts.map +1 -0
- package/dist/voice/voiceprint/identify.js +20 -0
- package/dist/voice/voiceprint/identify.js.map +1 -0
- package/dist/voice/voiceprint/liveness.d.ts +90 -0
- package/dist/voice/voiceprint/liveness.d.ts.map +1 -0
- package/dist/voice/voiceprint/liveness.js +251 -0
- package/dist/voice/voiceprint/liveness.js.map +1 -0
- package/dist/voice/voiceprint/match.d.ts +54 -0
- package/dist/voice/voiceprint/match.d.ts.map +1 -0
- package/dist/voice/voiceprint/match.js +88 -0
- package/dist/voice/voiceprint/match.js.map +1 -0
- package/dist/voice/voiceprint/providers/local-mfcc-stub.d.ts +44 -0
- package/dist/voice/voiceprint/providers/local-mfcc-stub.d.ts.map +1 -0
- package/dist/voice/voiceprint/providers/local-mfcc-stub.js +92 -0
- package/dist/voice/voiceprint/providers/local-mfcc-stub.js.map +1 -0
- package/dist/voice/voiceprint/store.d.ts +60 -0
- package/dist/voice/voiceprint/store.d.ts.map +1 -0
- package/dist/voice/voiceprint/store.js +155 -0
- package/dist/voice/voiceprint/store.js.map +1 -0
- package/dist/voice/voiceprint/trust.d.ts +90 -0
- package/dist/voice/voiceprint/trust.d.ts.map +1 -0
- package/dist/voice/voiceprint/trust.js +150 -0
- package/dist/voice/voiceprint/trust.js.map +1 -0
- package/dist/voice/voiceprint/types.d.ts +100 -0
- package/dist/voice/voiceprint/types.d.ts.map +1 -0
- package/dist/voice/voiceprint/types.js +23 -0
- package/dist/voice/voiceprint/types.js.map +1 -0
- package/dist/voice/wake.d.ts +64 -0
- package/dist/voice/wake.d.ts.map +1 -0
- package/dist/voice/wake.js +143 -0
- package/dist/voice/wake.js.map +1 -0
- package/docs/manuals/manual.ar.html +91 -0
- package/docs/manuals/manual.de.html +100 -0
- package/docs/manuals/manual.en.html +118 -0
- package/docs/manuals/manual.es.html +120 -0
- package/docs/manuals/manual.fr.html +102 -0
- package/docs/manuals/manual.hi.html +93 -0
- package/docs/manuals/manual.it.html +93 -0
- package/docs/manuals/manual.ja.html +97 -0
- package/docs/manuals/manual.pt.html +103 -0
- package/docs/manuals/manual.zh.html +89 -0
- package/package.json +94 -0
- package/src/i18n/catalogs/ar.json +86 -0
- package/src/i18n/catalogs/de.json +86 -0
- package/src/i18n/catalogs/en.json +86 -0
- package/src/i18n/catalogs/es.json +86 -0
- package/src/i18n/catalogs/fr.json +86 -0
- package/src/i18n/catalogs/hi.json +86 -0
- package/src/i18n/catalogs/it.json +86 -0
- package/src/i18n/catalogs/ja.json +86 -0
- package/src/i18n/catalogs/pt.json +86 -0
- package/src/i18n/catalogs/zh.json +86 -0
- package/templates/react-app/README.md +43 -0
- package/templates/react-app/index.html +12 -0
- package/templates/react-app/package.json +35 -0
- package/templates/react-app/src/App.tsx +106 -0
- package/templates/react-app/src/main.tsx +21 -0
- package/templates/react-app/src/nac/manifest.ts +46 -0
- package/templates/react-app/src/styles.css +68 -0
- package/templates/react-app/tsconfig.json +19 -0
- package/templates/react-app/vite.config.ts +12 -0
- package/templates/react-app/yujin.forge.json +15 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* src/migrate/ai-apply.ts
|
|
3
|
+
*
|
|
4
|
+
* Claude-powered JSX decoration. Per-file LLM call that adds
|
|
5
|
+
* data-nac-id / data-nac-role / data-nac-action attributes to JSX
|
|
6
|
+
* interactive elements, and returns a manifest fragment listing
|
|
7
|
+
* what it decorated. Forge aggregates all fragments into a single
|
|
8
|
+
* top-level manifest.json.
|
|
9
|
+
*
|
|
10
|
+
* The JSX-side counterpart to ai-decorator.ts (which works on
|
|
11
|
+
* static HTML). For React apps the source-level decoration is what
|
|
12
|
+
* persists across builds; static-HTML decoration would be lost on
|
|
13
|
+
* the next Vite build.
|
|
14
|
+
*
|
|
15
|
+
* Single-call per file. Sonnet 4.6 by default. temperature=0 for
|
|
16
|
+
* determinism (verified on calc baseline).
|
|
17
|
+
*
|
|
18
|
+
* ASCII-only.
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
23
|
+
import { validateAiApplyFragment, formatFindingsForRetry, } from './manifest-validator.js';
|
|
24
|
+
const DEFAULT_MODEL = 'claude-sonnet-4-6';
|
|
25
|
+
const PRICING = {
|
|
26
|
+
'claude-sonnet-4-6': { in: 3.00, out: 15.00 },
|
|
27
|
+
'claude-haiku-4-5': { in: 0.80, out: 4.00 },
|
|
28
|
+
'claude-haiku-4-5-20251001': { in: 0.80, out: 4.00 },
|
|
29
|
+
'claude-opus-4-7': { in: 15.00, out: 75.00 },
|
|
30
|
+
};
|
|
31
|
+
function resolveAnthropicModelId(id) {
|
|
32
|
+
if (id === 'claude-haiku-4-5')
|
|
33
|
+
return 'claude-haiku-4-5-20251001';
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
function computeCost(model, usage) {
|
|
37
|
+
const p = PRICING[model] ?? PRICING['claude-sonnet-4-6'];
|
|
38
|
+
return (usage.input * p.in + usage.output * p.out) / 1_000_000;
|
|
39
|
+
}
|
|
40
|
+
const SYSTEM_PROMPT = `You are a NAC-3 (Native Agent Contract) JSX decorator. You receive one React JSX file and return:
|
|
41
|
+
(a) the SAME file with data-nac-id / data-nac-role / data-nac-action attributes added to every interactive element (buttons, inputs, selects, links that DO something, tabs, navigation items, form fields).
|
|
42
|
+
(b) the manifest fragment listing what you decorated.
|
|
43
|
+
|
|
44
|
+
ROLE TAXONOMY (critical):
|
|
45
|
+
- action: anything the user CLICKS to make something happen.
|
|
46
|
+
- field: an element that HOLDS a VALUE the user can read or write (inputs, textareas, output panels). NEVER classify a value-holding element as region.
|
|
47
|
+
- region: a CONTAINER that groups other elements.
|
|
48
|
+
- option: a single choice inside a select / radio group.
|
|
49
|
+
- tab: a tab-bar entry.
|
|
50
|
+
- navigation: nav link that changes route.
|
|
51
|
+
|
|
52
|
+
ID PATTERN: "<plugin_slug>.<role>.<thing>", snake_case, lowercase. Example: "conduit.action.submit", "conduit.field.email", "conduit.region.feed".
|
|
53
|
+
|
|
54
|
+
VERB NAMING: short imperative (submit, login, follow, favorite, publish, delete, save). Avoid prefixes like "do_" or "btn_".
|
|
55
|
+
|
|
56
|
+
PLUGIN SLUG: choose ONE for the whole app. Use the same slug across every file. Default: derive from the project name or a clear semantic guess (e.g. "conduit" for a Medium clone, "todo" for a todo app).
|
|
57
|
+
|
|
58
|
+
PARENT-CHILD ID-PREFIX CONTINUITY (H5, critical when both files in scope):
|
|
59
|
+
|
|
60
|
+
When a PARENT file declares per-instance ids for a CHILD component it
|
|
61
|
+
renders (e.g. <FeedToggler> emits manifest entries
|
|
62
|
+
"conduit.tab.feed__your_feed", "conduit.tab.feed__global_feed" for
|
|
63
|
+
its rendered <FeedNavLink> children), the CHILD component's own
|
|
64
|
+
template-literal decoration MUST use the SAME prefix the parent
|
|
65
|
+
declared. Do NOT derive the child's prefix from the child's component
|
|
66
|
+
name.
|
|
67
|
+
|
|
68
|
+
In the FeedToggler/FeedNavLink example:
|
|
69
|
+
- PARENT FeedToggler.jsx declares per-instance ids using prefix
|
|
70
|
+
"conduit.tab.feed__" (NOT "conduit.tab.feed_nav_link__").
|
|
71
|
+
- CHILD FeedNavLink.jsx must therefore decorate as:
|
|
72
|
+
data-nac-id={\`conduit.tab.feed__\${slugify(name)}\`}
|
|
73
|
+
data-nac-role="tab"
|
|
74
|
+
data-nac-action="activate"
|
|
75
|
+
NOT as conduit.tab.feed_nav_link__\${name}.
|
|
76
|
+
- In the child's manifest "elements" array, the entry's
|
|
77
|
+
"instance_pattern" field carries the SAME parent-prefixed pattern:
|
|
78
|
+
"instance_pattern": "conduit.tab.feed__\${qualifier}"
|
|
79
|
+
|
|
80
|
+
This rule applies to ANY role that supports per-instance qualifiers
|
|
81
|
+
(tab, navigation, action, field, region). The parent's naming wins
|
|
82
|
+
because the parent is the file that knows the user-meaningful names
|
|
83
|
+
of the instances; the child is generic and reused across contexts.
|
|
84
|
+
|
|
85
|
+
To detect parent-declared prefixes from a child's file alone:
|
|
86
|
+
- If the child is imported by exactly one parent in the input set
|
|
87
|
+
AND that parent emits per-instance entries with a common prefix
|
|
88
|
+
"<plugin>.<role>.<X>__" (where X is shorter than the child's
|
|
89
|
+
own component name), use prefix "<plugin>.<role>.<X>__" for the
|
|
90
|
+
child's template, NOT "<plugin>.<role>.<child_component>__".
|
|
91
|
+
- If the parent is NOT in the input set or the prefix can't be
|
|
92
|
+
inferred, fall back to the child-component-name prefix BUT emit
|
|
93
|
+
a warning: "Child component decorated without parent-prefix
|
|
94
|
+
context; ids may not match parent's per-instance declarations."
|
|
95
|
+
|
|
96
|
+
This closes a structural gap previously assumed only at runtime
|
|
97
|
+
(NAC.click_by_verb base→qualified fallback). The fix belongs in
|
|
98
|
+
decoration, not runtime: runtime can't reconcile two prefix schemes
|
|
99
|
+
that don't share a root.
|
|
100
|
+
|
|
101
|
+
REUSABLE COMPONENTS -- PER-INSTANCE QUALIFIER (critical for SPAs):
|
|
102
|
+
|
|
103
|
+
Many React components are written once but rendered N times. If you decorate
|
|
104
|
+
<NavItem> as data-nac-id="conduit.navigation.nav_item", the DOM ends up with
|
|
105
|
+
N copies of the SAME id (Home, Sign in, Sign up, Settings -- all collide).
|
|
106
|
+
An agent cannot disambiguate "click Sign in" from "click Home" in that
|
|
107
|
+
collision.
|
|
108
|
+
|
|
109
|
+
The fix is per-instance qualifier suffixing on data-nac-id. Decide one of
|
|
110
|
+
three strategies BASED ON THE COMPONENT, not the parent:
|
|
111
|
+
|
|
112
|
+
1. STATIC: the component's identity is determined by a JSX prop / child
|
|
113
|
+
known at AUTHORING time (label="Home", to="/login", icon="trash"). Use
|
|
114
|
+
a JSX template literal that bakes the qualifier in:
|
|
115
|
+
data-nac-id={\`conduit.navigation.nav_item__\${slugify(label)}\`}
|
|
116
|
+
For this case, in your "elements" array, emit ONE entry per known
|
|
117
|
+
instance (look at how the parent uses the component to enumerate them
|
|
118
|
+
-- if the parent file is being decorated and uses <NavItem label="Home"/>,
|
|
119
|
+
<NavItem label="Sign in"/>, emit two entries:
|
|
120
|
+
conduit.navigation.nav_item__home
|
|
121
|
+
conduit.navigation.nav_item__sign_in
|
|
122
|
+
If you cannot see all the parents (the component is exported and the
|
|
123
|
+
parent file is not in this prompt), emit ONE element with the
|
|
124
|
+
"qualifier_source" pointing to the JSX expression you used:
|
|
125
|
+
{ "id": "conduit.navigation.nav_item",
|
|
126
|
+
"role": "navigation",
|
|
127
|
+
"actions": [{ "verb": "navigate" }],
|
|
128
|
+
"instance_pattern": "conduit.navigation.nav_item__\${qualifier}",
|
|
129
|
+
"qualifier_source": "props.label",
|
|
130
|
+
"warning": "static qualifier inferred from props.label; see parents for enumerated instances" }
|
|
131
|
+
|
|
132
|
+
2. DYNAMIC: the component's identity is from data that arrives at runtime
|
|
133
|
+
(an article slug, a comment id, a row's primary key). Use the JSX
|
|
134
|
+
expression that reads the data:
|
|
135
|
+
data-nac-id={\`conduit.action.favorite_article__\${article.slug}\`}
|
|
136
|
+
In your "elements" array, emit ONE template entry:
|
|
137
|
+
{ "id": "conduit.action.favorite_article",
|
|
138
|
+
"role": "action",
|
|
139
|
+
"actions": [{ "verb": "favorite" }],
|
|
140
|
+
"instance_pattern": "conduit.action.favorite_article__\${article.slug}",
|
|
141
|
+
"qualifier_source": "article.slug" }
|
|
142
|
+
|
|
143
|
+
3. UNINFERRABLE: you tried 1 and 2 and there is no obvious qualifier
|
|
144
|
+
(the component is reused but takes no identifying prop). Decorate with
|
|
145
|
+
the base id and emit a WARNING in the entry:
|
|
146
|
+
{ "id": "conduit.action.unknown_action",
|
|
147
|
+
"role": "action",
|
|
148
|
+
"actions": [{ "verb": "click" }],
|
|
149
|
+
"warning": "component is reusable but no qualifier could be inferred; N DOM instances will share this id; consider adding a label/id prop or use yf migrate --ai-override to disambiguate manually" }
|
|
150
|
+
NEVER silently collapse. The warning is what keeps the case study honest.
|
|
151
|
+
|
|
152
|
+
QUALIFIER SOURCES, in preference order:
|
|
153
|
+
1. props that identify content: \`label\`, \`name\`, \`id\`, \`slug\`, \`key\`, \`title\`
|
|
154
|
+
2. routing props: \`to\`, \`href\` (slugify the path)
|
|
155
|
+
3. primitive children: <Comp>Sign in</Comp> -> slugify the child text
|
|
156
|
+
4. semantic icon hints: icon="trash" -> qualifier "trash"
|
|
157
|
+
|
|
158
|
+
SLUGIFY: lowercase, replace whitespace + non-alphanum with "_", trim leading/trailing "_".
|
|
159
|
+
E.g. "Sign in" -> "sign_in", "/login" -> "login", "How to train your dragon" -> "how_to_train_your_dragon".
|
|
160
|
+
|
|
161
|
+
VERB NAMING when adding qualifier: keep the verb generic across instances
|
|
162
|
+
(verb="navigate" for all NavItems, not verb="navigate_to_home"). The
|
|
163
|
+
qualifier is what disambiguates instance; the verb is what disambiguates intent.
|
|
164
|
+
|
|
165
|
+
WHAT TO SKIP:
|
|
166
|
+
- Pure layout components (header / footer / div wrappers with no event handlers).
|
|
167
|
+
- Components that ONLY render children (no own interactive elements).
|
|
168
|
+
- Static text / images that have no onClick / onChange / form handlers.
|
|
169
|
+
|
|
170
|
+
WHAT TO PRESERVE:
|
|
171
|
+
- Every existing attribute, prop, JSX expression. You ONLY add.
|
|
172
|
+
- File structure, imports, exports.
|
|
173
|
+
- All formatting outside the touched elements.
|
|
174
|
+
|
|
175
|
+
ROUTE AWARENESS (H1 -- critical for SPA test generation):
|
|
176
|
+
|
|
177
|
+
If this file declares routes (matches React Router patterns
|
|
178
|
+
<Route path="/something" element={<Component />} />, or
|
|
179
|
+
<Routes>...children...</Routes>, or a route config object), capture
|
|
180
|
+
the URL path the element corresponds to.
|
|
181
|
+
|
|
182
|
+
For each element you decorate, if you can determine the route(s)
|
|
183
|
+
under which the component MOUNTS, emit a "mounted_at" array on the
|
|
184
|
+
element:
|
|
185
|
+
|
|
186
|
+
- For elements declared in routes/Home.jsx that mount under "/":
|
|
187
|
+
"mounted_at": ["/"]
|
|
188
|
+
- For elements declared in routes/Login.jsx that mount under "/login":
|
|
189
|
+
"mounted_at": ["/login"]
|
|
190
|
+
- For nav elements that live in a top-level Navbar always visible:
|
|
191
|
+
"mounted_at": ["*"] (the "always available" sentinel)
|
|
192
|
+
|
|
193
|
+
If you cannot determine the route (the component is a leaf rendered
|
|
194
|
+
by a parent you can't see), omit mounted_at. The aggregator will
|
|
195
|
+
attempt to infer it later from the import graph.
|
|
196
|
+
|
|
197
|
+
If this file IS a Router root (Routes container or a config that
|
|
198
|
+
declares <Route> children), also emit a top-level "route_map" field
|
|
199
|
+
mapping component import path -> URL pattern, e.g.:
|
|
200
|
+
"route_map": {
|
|
201
|
+
"./routes/Home": ["/"],
|
|
202
|
+
"./routes/Login": ["/login"],
|
|
203
|
+
"./routes/ArticleEditor": ["/editor", "/editor/:slug"]
|
|
204
|
+
}
|
|
205
|
+
The Router root file is also the natural place to emit "fixtures"
|
|
206
|
+
(see FIXTURE SEEDING below).
|
|
207
|
+
|
|
208
|
+
ROUTER KIND (H1.v4 -- critical for test URL emission):
|
|
209
|
+
Detect which Router the SPA uses and emit a top-level "router_kind"
|
|
210
|
+
field on the response:
|
|
211
|
+
- <BrowserRouter> -> "router_kind": "browser" (URLs like "/login")
|
|
212
|
+
- <HashRouter> -> "router_kind": "hash" (URLs like "/#/login")
|
|
213
|
+
- <MemoryRouter> -> "router_kind": "memory"
|
|
214
|
+
- default / unsure -> "router_kind": "browser"
|
|
215
|
+
The emitter prefixes test URLs with "#" when router_kind is "hash"
|
|
216
|
+
so cy.visit hits the right SPA route.
|
|
217
|
+
|
|
218
|
+
AUTH GATING (H1 -- critical for test pre-steps):
|
|
219
|
+
|
|
220
|
+
If a route is wrapped in <RequireAuth> or any obvious auth-guard
|
|
221
|
+
component, mark the route as auth-gated in "route_map":
|
|
222
|
+
"/editor": { "paths": ["/editor"], "requires_auth": true }
|
|
223
|
+
And for elements that mount only under auth-gated routes, set on
|
|
224
|
+
the element:
|
|
225
|
+
"requires_auth": true
|
|
226
|
+
|
|
227
|
+
FIXTURE SEEDING (H1.v3 -- critical for dynamic-route tests):
|
|
228
|
+
|
|
229
|
+
If this file is a Router root AND any route contains a path
|
|
230
|
+
parameter (\`:slug\`, \`:username\`, \`:id\`, etc), an e2e test on that
|
|
231
|
+
route cannot simply visit it -- the resource must exist first.
|
|
232
|
+
|
|
233
|
+
For each path parameter, infer the API endpoint that CREATES a
|
|
234
|
+
resource matching that parameter, by inspecting:
|
|
235
|
+
- the file you are decorating right now (Router roots often
|
|
236
|
+
import service modules)
|
|
237
|
+
- other src/services/*.js modules referenced in the imports
|
|
238
|
+
- the JSX of the route component (does it call useParams() with
|
|
239
|
+
a name that maps to this param?)
|
|
240
|
+
|
|
241
|
+
Emit "fixtures" inside route_map for each :param you can resolve.
|
|
242
|
+
Each fixture has a CREATE endpoint and (when possible) a DELETE
|
|
243
|
+
endpoint so each test starts from a clean state and tears down
|
|
244
|
+
after itself. Schema:
|
|
245
|
+
|
|
246
|
+
"fixtures": {
|
|
247
|
+
":slug": {
|
|
248
|
+
"create_endpoint": "POST /api/articles",
|
|
249
|
+
"create_payload": { "article": { "title": "Forge fixture",
|
|
250
|
+
"body": "Forge fixture body", "tagList": [] } },
|
|
251
|
+
"response_path": "article.slug",
|
|
252
|
+
"delete_endpoint": "DELETE /api/articles/{value}",
|
|
253
|
+
"requires_auth": true,
|
|
254
|
+
"unique_suffix_field": null
|
|
255
|
+
},
|
|
256
|
+
":username": {
|
|
257
|
+
"create_endpoint": "POST /api/users",
|
|
258
|
+
"create_payload": { "user": { "username": "forge_fixture_{seed}",
|
|
259
|
+
"email": "forge-fixture-{seed}@yujin.app",
|
|
260
|
+
"password": "forge-fixture-2026" } },
|
|
261
|
+
"response_path": "user.username",
|
|
262
|
+
"delete_endpoint": null,
|
|
263
|
+
"requires_auth": false,
|
|
264
|
+
"unique_suffix_field": "user.username,user.email"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Conventions:
|
|
269
|
+
- "create_endpoint" is "<METHOD> <PATH>" the SPA's service module
|
|
270
|
+
hits to CREATE the resource.
|
|
271
|
+
- "create_payload" is the minimal body for a 200/201. Use literal
|
|
272
|
+
values. When "unique_suffix_field" is set, the emitter replaces
|
|
273
|
+
{seed} with a DETERMINISTIC per-test suffix derived from the
|
|
274
|
+
test\'s position (NOT Math.random -- the suite must be
|
|
275
|
+
reproducible byte-for-byte across runs).
|
|
276
|
+
- "response_path" is the dotted path from response body to the
|
|
277
|
+
value that fills the URL param (e.g. "article.slug").
|
|
278
|
+
- "delete_endpoint" is "<METHOD> <PATH>" where "{value}" is
|
|
279
|
+
substituted with the captured response value. Set to null only
|
|
280
|
+
when truly no delete exists (e.g. user registration). When set,
|
|
281
|
+
the emitter calls it in afterEach with failOnStatusCode=false
|
|
282
|
+
so a failed test cleans up defensively.
|
|
283
|
+
- "unique_suffix_field" is a CSV list of dotted paths inside
|
|
284
|
+
create_payload where {seed} is substituted with the deterministic
|
|
285
|
+
per-test suffix (e.g. "user.username,user.email"). Required when
|
|
286
|
+
delete_endpoint is null AND the resource persists across tests.
|
|
287
|
+
- "requires_auth" is true when CREATE needs an auth header.
|
|
288
|
+
|
|
289
|
+
DETERMINISM (non-negotiable): never use random data. The emitter
|
|
290
|
+
will pass a deterministic SEED integer per test (the test index
|
|
291
|
+
within the suite) so the same suite produces the same payloads
|
|
292
|
+
across runs. Tests for the same element get the same fixture.
|
|
293
|
+
ISOLATION (non-negotiable): every test creates its own fixture in
|
|
294
|
+
beforeEach and tears it down in afterEach. No state leaks across
|
|
295
|
+
tests; the suite is repeatable from a fresh DB.
|
|
296
|
+
|
|
297
|
+
If you cannot determine a fixture for a given :param, omit it.
|
|
298
|
+
The emitter will skip the corresponding tests with a TODO.
|
|
299
|
+
|
|
300
|
+
OUTPUT FORMAT: a single JSON object, no prose, no markdown fences:
|
|
301
|
+
{
|
|
302
|
+
"decorated_jsx": "<the FULL decorated file content as a single string>",
|
|
303
|
+
"plugin_slug": "<slug>",
|
|
304
|
+
"route_map": "<optional: only when this file is a Router root>",
|
|
305
|
+
"fixtures": "<optional: only when this file is a Router root AND any route uses :params>",
|
|
306
|
+
"elements": [
|
|
307
|
+
{
|
|
308
|
+
"id": "<dotted id, possibly with __qualifier suffix>",
|
|
309
|
+
"role": "action|field|option|tab|region|navigation",
|
|
310
|
+
"actions": [{ "verb": "<verb>" }],
|
|
311
|
+
"instance_pattern": "<optional: present for DYNAMIC qualifiers>",
|
|
312
|
+
"qualifier_source": "<optional: JSX expression backing the qualifier>",
|
|
313
|
+
"warning": "<optional: present for UNINFERRABLE reusable components>",
|
|
314
|
+
"mounted_at": "<optional: array of URL paths where this element appears>",
|
|
315
|
+
"requires_auth": "<optional: true if any of its mount routes is auth-gated>"
|
|
316
|
+
}
|
|
317
|
+
],
|
|
318
|
+
"decorated": <true | false -- false if the file has no interactive elements>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
If the file has zero interactive elements to decorate, return:
|
|
322
|
+
{ "decorated_jsx": "<original file unchanged>", "plugin_slug": "<slug>", "elements": [], "decorated": false }
|
|
323
|
+
`;
|
|
324
|
+
/* I18N_PROMPT_EXTENSION -- appended to SYSTEM_PROMPT when the
|
|
325
|
+
--with-i18n-tags flag is set. Adds data-i18n="<key>" decoration
|
|
326
|
+
on user-facing text nodes IN PARALLEL with the existing NAC-3
|
|
327
|
+
decoration. The two attribute systems are independent: NAC ids
|
|
328
|
+
are for AGENTS (semantic, language-invariant); i18n keys are for
|
|
329
|
+
HUMAN VIEWERS (translated per locale at runtime via the host's
|
|
330
|
+
i18n shim).
|
|
331
|
+
|
|
332
|
+
Do NOT decorate the SAME element with conflicting keys. Do not
|
|
333
|
+
collide i18n keys across files (use the file's component context
|
|
334
|
+
as a namespace, e.g. "navbar.home", "login.sign_in_button"). */
|
|
335
|
+
const I18N_PROMPT_EXTENSION = `
|
|
336
|
+
I18N DECORATION (ADDITIONAL, runs in parallel with NAC-3 decoration):
|
|
337
|
+
|
|
338
|
+
For every JSX text node that is user-facing (button text, link text,
|
|
339
|
+
heading text, paragraph text, label text, placeholder, title attribute,
|
|
340
|
+
aria-label) AND that is a LITERAL STRING (not derived from a prop /
|
|
341
|
+
variable / expression), add:
|
|
342
|
+
|
|
343
|
+
- "data-i18n" attribute on the text-containing element, holding a
|
|
344
|
+
stable snake_case key. Format: "<surface>.<element>" where surface
|
|
345
|
+
is the namespace (nav, feed, profile, form, action, msg) and element
|
|
346
|
+
describes the specific text (sign_in, home, password, etc).
|
|
347
|
+
- "data-i18n-placeholder" attribute for placeholder= attributes.
|
|
348
|
+
- "data-i18n-title" attribute for title= attributes.
|
|
349
|
+
- "data-i18n-aria-label" for aria-label= attributes.
|
|
350
|
+
|
|
351
|
+
Example transformations:
|
|
352
|
+
Before: <button>Sign in</button>
|
|
353
|
+
After: <button data-nac-id="conduit.action.sign_in"
|
|
354
|
+
data-nac-role="action" data-nac-action="login"
|
|
355
|
+
data-i18n="action.sign_in">Sign in</button>
|
|
356
|
+
|
|
357
|
+
Before: <input placeholder="Email" />
|
|
358
|
+
After: <input data-nac-id="conduit.field.email"
|
|
359
|
+
data-nac-role="field"
|
|
360
|
+
data-i18n-placeholder="form.email"
|
|
361
|
+
placeholder="Email" />
|
|
362
|
+
|
|
363
|
+
DO NOT decorate:
|
|
364
|
+
- Text that is a prop / variable / expression / template (\`\${...}\`).
|
|
365
|
+
Those are dynamic and live in the rendered data, not the catalog.
|
|
366
|
+
- Text inside <code> / <pre> blocks (technical content, language-agnostic).
|
|
367
|
+
- Single-character symbols (×, ✓, +, etc).
|
|
368
|
+
- Numerals that are computed (counts, prices). Static numerals in
|
|
369
|
+
copy ARE decorated.
|
|
370
|
+
|
|
371
|
+
Return an ADDITIONAL field in the JSON response:
|
|
372
|
+
|
|
373
|
+
"i18n_catalog": {
|
|
374
|
+
"action.sign_in": "Sign in",
|
|
375
|
+
"form.email": "Email",
|
|
376
|
+
"msg.tagline": "A place to share your knowledge.",
|
|
377
|
+
...
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
Keys MUST match what you placed in data-i18n / data-i18n-placeholder /
|
|
381
|
+
data-i18n-title / data-i18n-aria-label attributes. Values are the
|
|
382
|
+
ENGLISH source text (the original verbatim string from the JSX). The
|
|
383
|
+
catalog is per-file; the project-level aggregator merges them.
|
|
384
|
+
|
|
385
|
+
If the file has no user-facing literal text to decorate, return
|
|
386
|
+
"i18n_catalog": {} (an empty object).
|
|
387
|
+
`;
|
|
388
|
+
const _client = {};
|
|
389
|
+
function client() {
|
|
390
|
+
if (!_client.c)
|
|
391
|
+
_client.c = new Anthropic();
|
|
392
|
+
return _client.c;
|
|
393
|
+
}
|
|
394
|
+
function extractJsonObject(text) {
|
|
395
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
|
|
396
|
+
if (fenced && fenced[1])
|
|
397
|
+
return fenced[1].trim();
|
|
398
|
+
const trimmed = text.trim();
|
|
399
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}'))
|
|
400
|
+
return trimmed;
|
|
401
|
+
const i = trimmed.indexOf('{');
|
|
402
|
+
const j = trimmed.lastIndexOf('}');
|
|
403
|
+
if (i >= 0 && j > i)
|
|
404
|
+
return trimmed.slice(i, j + 1);
|
|
405
|
+
return trimmed;
|
|
406
|
+
}
|
|
407
|
+
/* G2: maximum LLM attempts per file before we accept the last
|
|
408
|
+
response as-is and surface validation findings to the caller. */
|
|
409
|
+
export const AI_APPLY_MAX_ATTEMPTS = 3;
|
|
410
|
+
const defaultAiCaller = async (args) => {
|
|
411
|
+
const payload = {
|
|
412
|
+
model: args.model,
|
|
413
|
+
max_tokens: 8192,
|
|
414
|
+
system: args.system,
|
|
415
|
+
messages: args.messages,
|
|
416
|
+
};
|
|
417
|
+
if (!args.skipTemperature)
|
|
418
|
+
payload.temperature = 0;
|
|
419
|
+
const resp = await client().messages.create(payload);
|
|
420
|
+
const block = resp.content.find((b) => b.type === 'text');
|
|
421
|
+
return {
|
|
422
|
+
text: block?.text || '',
|
|
423
|
+
usage: {
|
|
424
|
+
input: resp.usage.input_tokens || 0,
|
|
425
|
+
output: resp.usage.output_tokens || 0,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* G2: assemble the retry-turn user message. Includes the prior
|
|
431
|
+
* findings + the last attempt's raw output so Claude can target a
|
|
432
|
+
* fix instead of guessing what went wrong.
|
|
433
|
+
*
|
|
434
|
+
* `attempt` is the zero-based index of the JUST-FAILED attempt;
|
|
435
|
+
* the next attempt the model is about to make is therefore
|
|
436
|
+
* `attempt + 2` in human-counted "1 of N" terms.
|
|
437
|
+
*/
|
|
438
|
+
function buildRetryUserMessage(args) {
|
|
439
|
+
return 'Your previous response failed contract validation. Findings:\n'
|
|
440
|
+
+ formatFindingsForRetry(args.findings)
|
|
441
|
+
+ '\n\nThis is retry attempt ' + (args.attempt + 2) + ' of '
|
|
442
|
+
+ AI_APPLY_MAX_ATTEMPTS + '. Please re-emit the COMPLETE JSON object '
|
|
443
|
+
+ '(decorated_jsx, elements, plugin_slug, etc) with the listed errors fixed. '
|
|
444
|
+
+ 'Do not omit fields that were already correct.';
|
|
445
|
+
}
|
|
446
|
+
export async function aiApplyOne(opts) {
|
|
447
|
+
const model = opts.model || DEFAULT_MODEL;
|
|
448
|
+
const skipTemp = /^claude-opus-4-7/.test(model);
|
|
449
|
+
const system = opts.withI18n ? (SYSTEM_PROMPT + '\n\n' + I18N_PROMPT_EXTENSION) : SYSTEM_PROMPT;
|
|
450
|
+
const initialUserPrompt = '<FILE path="' + opts.relpath + '">\n' + opts.jsxSource + '\n</FILE>' +
|
|
451
|
+
(opts.pluginSlugHint ? '\n\nPlugin slug for this project: ' + opts.pluginSlugHint : '') +
|
|
452
|
+
(opts.withI18n ? '\n\nALSO emit i18n decoration per the i18n section of the system prompt.' : '');
|
|
453
|
+
const caller = opts.aiCaller || defaultAiCaller;
|
|
454
|
+
const t0 = Date.now();
|
|
455
|
+
let tokens_in = 0;
|
|
456
|
+
let tokens_out = 0;
|
|
457
|
+
const messages = [
|
|
458
|
+
{ role: 'user', content: initialUserPrompt },
|
|
459
|
+
];
|
|
460
|
+
let lastParsed = null;
|
|
461
|
+
let lastRaw = '';
|
|
462
|
+
let lastFindings = [];
|
|
463
|
+
let attempts = 0;
|
|
464
|
+
for (let attempt = 0; attempt < AI_APPLY_MAX_ATTEMPTS; attempt++) {
|
|
465
|
+
attempts = attempt + 1;
|
|
466
|
+
const result = await caller({
|
|
467
|
+
system,
|
|
468
|
+
messages,
|
|
469
|
+
model: resolveAnthropicModelId(model),
|
|
470
|
+
skipTemperature: skipTemp,
|
|
471
|
+
});
|
|
472
|
+
lastRaw = result.text;
|
|
473
|
+
tokens_in += result.usage.input;
|
|
474
|
+
tokens_out += result.usage.output;
|
|
475
|
+
const jsonText = extractJsonObject(lastRaw);
|
|
476
|
+
let parsed;
|
|
477
|
+
let parseFailed = false;
|
|
478
|
+
try {
|
|
479
|
+
parsed = JSON.parse(jsonText);
|
|
480
|
+
}
|
|
481
|
+
catch (_e) {
|
|
482
|
+
parseFailed = true;
|
|
483
|
+
}
|
|
484
|
+
if (parseFailed) {
|
|
485
|
+
lastParsed = null;
|
|
486
|
+
lastFindings = [{
|
|
487
|
+
severity: 'error',
|
|
488
|
+
code: 'response_not_json',
|
|
489
|
+
message: 'Response did not contain a parseable JSON object. ' +
|
|
490
|
+
'Wrap the result in a single { ... } object (or a single ```json fenced block).',
|
|
491
|
+
}];
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
lastParsed = parsed;
|
|
495
|
+
const validation = validateAiApplyFragment(parsed);
|
|
496
|
+
lastFindings = validation.findings;
|
|
497
|
+
if (validation.ok) {
|
|
498
|
+
// Success: break the loop, return below.
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Failed attempt: prepare retry messages if there is a next round.
|
|
503
|
+
if (attempt + 1 < AI_APPLY_MAX_ATTEMPTS) {
|
|
504
|
+
messages.push({ role: 'assistant', content: lastRaw });
|
|
505
|
+
messages.push({
|
|
506
|
+
role: 'user',
|
|
507
|
+
content: buildRetryUserMessage({ findings: lastFindings, attempt }),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const latency_ms = Date.now() - t0;
|
|
512
|
+
const cost_usd = computeCost(model, { input: tokens_in, output: tokens_out });
|
|
513
|
+
/* Final fragment: if parsing failed all attempts, fall back to the
|
|
514
|
+
original source with no decorations. Otherwise reflect whatever
|
|
515
|
+
the last attempt produced -- the caller decides whether to act
|
|
516
|
+
on validation_findings (warn vs block) at aggregation time. */
|
|
517
|
+
if (!lastParsed) {
|
|
518
|
+
return {
|
|
519
|
+
relpath: opts.relpath,
|
|
520
|
+
decorated_jsx: opts.jsxSource,
|
|
521
|
+
elements: [],
|
|
522
|
+
plugin_slug_guess: opts.pluginSlugHint || 'unknown',
|
|
523
|
+
tokens_in,
|
|
524
|
+
tokens_out,
|
|
525
|
+
cost_usd,
|
|
526
|
+
latency_ms,
|
|
527
|
+
decorated: false,
|
|
528
|
+
attempts,
|
|
529
|
+
validation_findings: lastFindings,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
relpath: opts.relpath,
|
|
534
|
+
decorated_jsx: lastParsed.decorated_jsx || opts.jsxSource,
|
|
535
|
+
elements: Array.isArray(lastParsed.elements) ? lastParsed.elements : [],
|
|
536
|
+
plugin_slug_guess: lastParsed.plugin_slug || opts.pluginSlugHint || 'unknown',
|
|
537
|
+
route_map: (lastParsed.route_map && typeof lastParsed.route_map === 'object') ? lastParsed.route_map : undefined,
|
|
538
|
+
fixtures: (lastParsed.fixtures && typeof lastParsed.fixtures === 'object') ? lastParsed.fixtures : undefined,
|
|
539
|
+
router_kind: (lastParsed.router_kind === 'hash' || lastParsed.router_kind === 'memory' || lastParsed.router_kind === 'browser')
|
|
540
|
+
? lastParsed.router_kind
|
|
541
|
+
: undefined,
|
|
542
|
+
i18n_catalog: (lastParsed.i18n_catalog && typeof lastParsed.i18n_catalog === 'object') ? lastParsed.i18n_catalog : undefined,
|
|
543
|
+
tokens_in,
|
|
544
|
+
tokens_out,
|
|
545
|
+
cost_usd,
|
|
546
|
+
latency_ms,
|
|
547
|
+
decorated: !!lastParsed.decorated,
|
|
548
|
+
attempts,
|
|
549
|
+
validation_findings: lastFindings,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Walk a project directory, decorate every JSX/TSX file, aggregate
|
|
554
|
+
* into a single manifest. Writes decorated files back in place (or
|
|
555
|
+
* to outDir when set). Sequential by default to keep API budgets
|
|
556
|
+
* predictable; can run with bounded concurrency via the
|
|
557
|
+
* `concurrency` option.
|
|
558
|
+
*/
|
|
559
|
+
export async function aiApplyProject(opts) {
|
|
560
|
+
const root = opts.subdir
|
|
561
|
+
? path.join(opts.projectRoot, opts.subdir)
|
|
562
|
+
: opts.projectRoot;
|
|
563
|
+
/* Collect .jsx / .tsx files. */
|
|
564
|
+
const files = [];
|
|
565
|
+
async function walk(dir) {
|
|
566
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
567
|
+
for (const e of entries) {
|
|
568
|
+
const p = path.join(dir, e.name);
|
|
569
|
+
if (e.isDirectory()) {
|
|
570
|
+
if (e.name === 'node_modules' || e.name === '.git' || e.name === 'dist')
|
|
571
|
+
continue;
|
|
572
|
+
await walk(p);
|
|
573
|
+
}
|
|
574
|
+
else if (/\.(jsx|tsx)$/.test(e.name)) {
|
|
575
|
+
files.push(p);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
await walk(root);
|
|
580
|
+
const results = new Array(files.length);
|
|
581
|
+
const conc = Math.max(1, Math.min(opts.concurrency || 4, 8));
|
|
582
|
+
let next = 0;
|
|
583
|
+
async function worker() {
|
|
584
|
+
while (true) {
|
|
585
|
+
const i = next++;
|
|
586
|
+
if (i >= files.length)
|
|
587
|
+
return;
|
|
588
|
+
const abs = files[i];
|
|
589
|
+
const rel = path.relative(opts.projectRoot, abs);
|
|
590
|
+
const src = await fs.readFile(abs, 'utf-8');
|
|
591
|
+
const r = await aiApplyOne({
|
|
592
|
+
jsxSource: src,
|
|
593
|
+
relpath: rel,
|
|
594
|
+
pluginSlugHint: opts.pluginSlugHint,
|
|
595
|
+
model: opts.model,
|
|
596
|
+
withI18n: opts.withI18n,
|
|
597
|
+
});
|
|
598
|
+
results[i] = r;
|
|
599
|
+
if (opts.onProgress)
|
|
600
|
+
opts.onProgress(i + 1, files.length, rel, r);
|
|
601
|
+
if (!opts.dryRun && r.decorated) {
|
|
602
|
+
const dst = opts.outDir
|
|
603
|
+
? path.join(opts.outDir, rel)
|
|
604
|
+
: abs;
|
|
605
|
+
await fs.mkdir(path.dirname(dst), { recursive: true });
|
|
606
|
+
await fs.writeFile(dst, r.decorated_jsx, 'utf-8');
|
|
607
|
+
}
|
|
608
|
+
else if (opts.outDir && !opts.dryRun) {
|
|
609
|
+
/* When outputting to a separate dir, copy the file verbatim
|
|
610
|
+
even if undecorated so the project is complete. */
|
|
611
|
+
const outBase = opts.outDir;
|
|
612
|
+
const dst = path.join(outBase, rel);
|
|
613
|
+
await fs.mkdir(path.dirname(dst), { recursive: true });
|
|
614
|
+
await fs.writeFile(dst, src, 'utf-8');
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const workers = Array.from({ length: conc }, () => worker());
|
|
619
|
+
await Promise.all(workers);
|
|
620
|
+
/* Pick canonical plugin slug: most common across non-empty
|
|
621
|
+
decorated files; tiebreak by first occurrence. */
|
|
622
|
+
const slugCount = {};
|
|
623
|
+
for (const r of results) {
|
|
624
|
+
if (!r || !r.plugin_slug_guess)
|
|
625
|
+
continue;
|
|
626
|
+
slugCount[r.plugin_slug_guess] = (slugCount[r.plugin_slug_guess] || 0) + 1;
|
|
627
|
+
}
|
|
628
|
+
let canonicalSlug = opts.pluginSlugHint || 'app';
|
|
629
|
+
let bestCount = -1;
|
|
630
|
+
for (const slug of Object.keys(slugCount)) {
|
|
631
|
+
const cnt = slugCount[slug] ?? 0;
|
|
632
|
+
if (cnt > bestCount) {
|
|
633
|
+
canonicalSlug = slug;
|
|
634
|
+
bestCount = cnt;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const combinedRouteMap = {};
|
|
638
|
+
for (const r of results) {
|
|
639
|
+
if (!r || !r.route_map)
|
|
640
|
+
continue;
|
|
641
|
+
for (const k of Object.keys(r.route_map)) {
|
|
642
|
+
const v = r.route_map[k];
|
|
643
|
+
if (v === undefined)
|
|
644
|
+
continue;
|
|
645
|
+
const norm = Array.isArray(v)
|
|
646
|
+
? { paths: v, requires_auth: false }
|
|
647
|
+
: { paths: v.paths || [], requires_auth: !!v.requires_auth };
|
|
648
|
+
const existing = combinedRouteMap[k];
|
|
649
|
+
if (existing) {
|
|
650
|
+
existing.paths = Array.from(new Set([...existing.paths, ...norm.paths]));
|
|
651
|
+
existing.requires_auth = existing.requires_auth || norm.requires_auth;
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
combinedRouteMap[k] = norm;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/* H1: infer mounted_at for elements that didn't declare it. The
|
|
659
|
+
heuristic: if the element's file path matches one of the
|
|
660
|
+
route_map keys, propagate the paths + requires_auth. */
|
|
661
|
+
function inferMountedFor(file) {
|
|
662
|
+
const candidates = Object.keys(combinedRouteMap);
|
|
663
|
+
const norm = file.replace(/\\/g, '/').replace(/^src\//, '').replace(/\.(jsx|tsx)$/, '');
|
|
664
|
+
for (const k of candidates) {
|
|
665
|
+
const nk = k.replace(/^\.\//, '').replace(/\.(jsx|tsx)$/, '');
|
|
666
|
+
if (norm === nk || norm.endsWith('/' + nk) || nk.endsWith('/' + norm)) {
|
|
667
|
+
return combinedRouteMap[k] || null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
/* Aggregate elements. Dedupe by id (later file wins, but warn).
|
|
673
|
+
H2: collect warnings from individual elements (reusable components
|
|
674
|
+
without qualifier) for surfacing at end-of-apply.
|
|
675
|
+
H1: fill mounted_at + requires_auth from route_map where the
|
|
676
|
+
element omitted them. */
|
|
677
|
+
const seen = {};
|
|
678
|
+
const elements = [];
|
|
679
|
+
const warnings = [];
|
|
680
|
+
for (const r of results) {
|
|
681
|
+
if (!r || !r.elements)
|
|
682
|
+
continue;
|
|
683
|
+
const fileInfer = inferMountedFor(r.relpath);
|
|
684
|
+
for (const el of r.elements) {
|
|
685
|
+
if (!el || !el.id)
|
|
686
|
+
continue;
|
|
687
|
+
if (el.warning) {
|
|
688
|
+
warnings.push({ file: r.relpath, id: el.id, warning: el.warning });
|
|
689
|
+
}
|
|
690
|
+
/* H1: fill mounted_at + requires_auth if missing. */
|
|
691
|
+
if ((!el.mounted_at || el.mounted_at.length === 0) && fileInfer) {
|
|
692
|
+
el.mounted_at = fileInfer.paths;
|
|
693
|
+
if (fileInfer.requires_auth && el.requires_auth === undefined) {
|
|
694
|
+
el.requires_auth = true;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (seen[el.id]) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
seen[el.id] = r;
|
|
701
|
+
elements.push(el);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/* H1.v3: aggregate fixtures across files. Multiple Router roots
|
|
705
|
+
could declare fixtures for the same :param; last write wins. */
|
|
706
|
+
const fixtures = {};
|
|
707
|
+
for (const r of results) {
|
|
708
|
+
if (!r || !r.fixtures)
|
|
709
|
+
continue;
|
|
710
|
+
for (const k of Object.keys(r.fixtures)) {
|
|
711
|
+
fixtures[k] = r.fixtures[k];
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/* H1.v4: aggregate router_kind. Take the first non-undefined value
|
|
715
|
+
across all files. "hash" wins over "browser" if both were detected
|
|
716
|
+
(the Router root file's value is authoritative; this is just a
|
|
717
|
+
defensive fallback). */
|
|
718
|
+
let router_kind = undefined;
|
|
719
|
+
for (const r of results) {
|
|
720
|
+
if (!r || !r.router_kind)
|
|
721
|
+
continue;
|
|
722
|
+
if (!router_kind)
|
|
723
|
+
router_kind = r.router_kind;
|
|
724
|
+
else if (r.router_kind === 'hash')
|
|
725
|
+
router_kind = 'hash';
|
|
726
|
+
}
|
|
727
|
+
/* i18n: aggregate per-file catalogs into a single EN canonical, then
|
|
728
|
+
write i18n/<lang>.json under outDir (or projectRoot when outDir not
|
|
729
|
+
set). The EN catalog is the source of truth; other languages start
|
|
730
|
+
as a CLONE of EN so the keys are present; humans / downstream tools
|
|
731
|
+
translate the values. Collisions (same key, different value across
|
|
732
|
+
files) are resolved last-write-wins with a warning. */
|
|
733
|
+
const i18nCanonical = {};
|
|
734
|
+
const i18nWarnings = [];
|
|
735
|
+
if (opts.withI18n) {
|
|
736
|
+
for (const r of results) {
|
|
737
|
+
if (!r || !r.i18n_catalog)
|
|
738
|
+
continue;
|
|
739
|
+
for (const k of Object.keys(r.i18n_catalog)) {
|
|
740
|
+
const v = r.i18n_catalog[k];
|
|
741
|
+
if (v == null)
|
|
742
|
+
continue;
|
|
743
|
+
if (i18nCanonical[k] != null && i18nCanonical[k] !== v) {
|
|
744
|
+
i18nWarnings.push({
|
|
745
|
+
file: r.relpath,
|
|
746
|
+
key: k,
|
|
747
|
+
old_value: i18nCanonical[k],
|
|
748
|
+
new_value: v,
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
i18nCanonical[k] = v;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (!opts.dryRun) {
|
|
755
|
+
const langs = (opts.i18nLangs && opts.i18nLangs.length > 0)
|
|
756
|
+
? opts.i18nLangs
|
|
757
|
+
: ['en', 'es', 'pt', 'fr', 'it', 'de', 'ja', 'zh', 'hi', 'ar'];
|
|
758
|
+
const targetRoot = opts.outDir || opts.projectRoot;
|
|
759
|
+
const i18nDir = path.join(targetRoot, 'i18n');
|
|
760
|
+
await fs.mkdir(i18nDir, { recursive: true });
|
|
761
|
+
/* en.json -- authoritative source from the AI extraction. */
|
|
762
|
+
await fs.writeFile(path.join(i18nDir, 'en.json'), JSON.stringify(i18nCanonical, null, 2), 'utf-8');
|
|
763
|
+
/* Other locales -- start as a clone of EN. The user fills in
|
|
764
|
+
translations downstream (manually or by another LLM pass).
|
|
765
|
+
An empty stub {} is misleading -- it would make the runtime
|
|
766
|
+
shim fall back to EN silently. Cloning EN makes the
|
|
767
|
+
missing-translation state visible (the value is still EN,
|
|
768
|
+
not blank). */
|
|
769
|
+
for (const lang of langs) {
|
|
770
|
+
if (lang === 'en')
|
|
771
|
+
continue;
|
|
772
|
+
const langPath = path.join(i18nDir, lang + '.json');
|
|
773
|
+
/* If the file already exists, DO NOT overwrite -- it may
|
|
774
|
+
contain real translations the user already authored. */
|
|
775
|
+
try {
|
|
776
|
+
await fs.access(langPath);
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
catch {
|
|
780
|
+
/* not found -- fall through and write the EN clone */
|
|
781
|
+
}
|
|
782
|
+
await fs.writeFile(langPath, JSON.stringify(i18nCanonical, null, 2), 'utf-8');
|
|
783
|
+
}
|
|
784
|
+
/* index.json -- declares the available locale list for the
|
|
785
|
+
runtime shim. */
|
|
786
|
+
const indexPath = path.join(i18nDir, 'index.json');
|
|
787
|
+
try {
|
|
788
|
+
await fs.access(indexPath);
|
|
789
|
+
/* leave the existing index alone -- the user may have customised it */
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
const NATIVE_NAMES = {
|
|
793
|
+
en: 'English', es: 'Español', pt: 'Português', fr: 'Français',
|
|
794
|
+
it: 'Italiano', de: 'Deutsch', ja: '日本語', zh: '中文',
|
|
795
|
+
hi: 'हिन्दी', ar: 'العربية',
|
|
796
|
+
};
|
|
797
|
+
const index = {
|
|
798
|
+
default: 'en',
|
|
799
|
+
available: langs.map(l => ({
|
|
800
|
+
code: l,
|
|
801
|
+
name: l.toUpperCase(),
|
|
802
|
+
native: NATIVE_NAMES[l] || l,
|
|
803
|
+
})),
|
|
804
|
+
};
|
|
805
|
+
await fs.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
files: results,
|
|
811
|
+
manifest: {
|
|
812
|
+
plugin_slug: canonicalSlug,
|
|
813
|
+
version: '1.0.0',
|
|
814
|
+
nac_version: '1.0',
|
|
815
|
+
label_i18n: { es: canonicalSlug, en: canonicalSlug },
|
|
816
|
+
elements,
|
|
817
|
+
...(Object.keys(fixtures).length > 0 ? { fixtures } : {}),
|
|
818
|
+
...(router_kind ? { router_kind } : {}),
|
|
819
|
+
},
|
|
820
|
+
warnings,
|
|
821
|
+
i18n_catalog: opts.withI18n ? i18nCanonical : undefined,
|
|
822
|
+
i18n_warnings: opts.withI18n ? i18nWarnings : undefined,
|
|
823
|
+
total_tokens_in: results.reduce((a, b) => a + (b?.tokens_in || 0), 0),
|
|
824
|
+
total_tokens_out: results.reduce((a, b) => a + (b?.tokens_out || 0), 0),
|
|
825
|
+
total_cost_usd: results.reduce((a, b) => a + (b?.cost_usd || 0), 0),
|
|
826
|
+
total_latency_ms: results.reduce((a, b) => a + (b?.latency_ms || 0), 0),
|
|
827
|
+
files_clean_first_attempt: results.reduce((a, b) => a + ((b?.attempts === 1 && (b?.validation_findings?.length || 0) === 0) ? 1 : 0), 0),
|
|
828
|
+
files_retried: results.reduce((a, b) => a + (((b?.attempts || 0) > 1) ? 1 : 0), 0),
|
|
829
|
+
files_gave_up: results.reduce((a, b) => a + (((b?.attempts || 0) >= AI_APPLY_MAX_ATTEMPTS
|
|
830
|
+
&& (b?.validation_findings?.filter((f) => f.severity === 'error').length || 0) > 0) ? 1 : 0), 0),
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
//# sourceMappingURL=ai-apply.js.map
|