codeply-cli 0.5.5 → 0.6.0
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 +21 -21
- package/THIRD_PARTY_NOTICES.md +65 -65
- package/bin/codeply.js +8 -3
- package/lib/agent.mjs +2596 -2268
- package/lib/ai.js +135 -27
- package/lib/ai.mjs +6 -6
- package/lib/apply-patch.js +217 -217
- package/lib/arity-table.json +138 -138
- package/lib/arity.js +70 -70
- package/lib/auth.mjs +6 -6
- package/lib/autopilot.js +84 -0
- package/lib/bot-mail.js +46 -0
- package/lib/bots.js +142 -15
- package/lib/brain.js +914 -0
- package/lib/call-assist.js +120 -0
- package/lib/chatgpt.js +282 -282
- package/lib/cloud-browser.mjs +143 -143
- package/lib/cloud.mjs +1396 -1312
- package/lib/commands.js +103 -103
- package/lib/computer.js +136 -0
- package/lib/design-library/assemble-artifact.mjs +21 -21
- package/lib/design-library/cleanup.mjs +45 -45
- package/lib/design-library/index.json +22379 -22379
- package/lib/design-library/make-table-data.mjs +39 -39
- package/lib/design-tools.mjs +173 -0
- package/lib/diagnostics.js +192 -192
- package/lib/dsh-acp.mjs +444 -0
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +326 -326
- package/lib/handoff.mjs +245 -0
- package/lib/history.js +78 -75
- package/lib/life-sources.js +319 -0
- package/lib/life-store.js +356 -0
- package/lib/life-triggers.js +286 -0
- package/lib/life.js +487 -0
- package/lib/mail-watch.js +3 -3
- package/lib/mcp.js +258 -258
- package/lib/native-tools.mjs +17 -5
- package/lib/oauth-connectors.js +76 -21
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +356 -353
- package/lib/reach.js +67 -67
- package/lib/render-md.mjs +647 -617
- package/lib/screen.js +77 -0
- package/lib/server.mjs +458 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +208 -168
- package/lib/skills.js +4 -1
- package/lib/snapshot.js +146 -146
- package/lib/subagents.js +4 -2
- package/lib/telemetry.js +105 -105
- package/lib/terminal-hints.js +154 -154
- package/lib/textarea.mjs +264 -258
- package/lib/tools.mjs +326 -40
- package/lib/tui.mjs +1459 -1086
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +209 -209
- package/package.json +1 -1
- package/skills/SOURCE.md +58 -58
- package/skills/accessibility/SKILL.md +147 -147
- package/skills/agent-architecture-audit/SKILL.md +257 -257
- package/skills/agent-eval/SKILL.md +147 -147
- package/skills/agent-harness-construction/SKILL.md +74 -74
- package/skills/agent-introspection-debugging/SKILL.md +154 -154
- package/skills/agent-payment-x402/SKILL.md +225 -225
- package/skills/agent-self-evaluation/SKILL.md +182 -182
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
- package/skills/agent-sort/SKILL.md +216 -216
- package/skills/agentic-engineering/SKILL.md +64 -64
- package/skills/agentic-os/SKILL.md +388 -388
- package/skills/ai-first-engineering/SKILL.md +52 -52
- package/skills/ai-regression-testing/SKILL.md +386 -386
- package/skills/android-clean-architecture/SKILL.md +340 -340
- package/skills/angular-developer/SKILL.md +155 -155
- package/skills/angular-developer/references/angular-animations.md +160 -160
- package/skills/angular-developer/references/angular-aria.md +410 -410
- package/skills/angular-developer/references/cli.md +86 -86
- package/skills/angular-developer/references/component-harnesses.md +59 -59
- package/skills/angular-developer/references/component-styling.md +91 -91
- package/skills/angular-developer/references/components.md +117 -117
- package/skills/angular-developer/references/creating-services.md +97 -97
- package/skills/angular-developer/references/data-resolvers.md +69 -69
- package/skills/angular-developer/references/define-routes.md +67 -67
- package/skills/angular-developer/references/defining-providers.md +72 -72
- package/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/skills/angular-developer/references/e2e-testing.md +56 -56
- package/skills/angular-developer/references/effects.md +83 -83
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/skills/angular-developer/references/host-elements.md +80 -80
- package/skills/angular-developer/references/injection-context.md +63 -63
- package/skills/angular-developer/references/inputs.md +101 -101
- package/skills/angular-developer/references/linked-signal.md +59 -59
- package/skills/angular-developer/references/loading-strategies.md +61 -61
- package/skills/angular-developer/references/mcp.md +108 -108
- package/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/skills/angular-developer/references/outputs.md +86 -86
- package/skills/angular-developer/references/reactive-forms.md +122 -122
- package/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/skills/angular-developer/references/resource.md +77 -77
- package/skills/angular-developer/references/route-animations.md +56 -56
- package/skills/angular-developer/references/route-guards.md +52 -52
- package/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/skills/angular-developer/references/router-testing.md +87 -87
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/skills/angular-developer/references/signal-forms.md +795 -795
- package/skills/angular-developer/references/signals-overview.md +94 -94
- package/skills/angular-developer/references/tailwind-css.md +69 -69
- package/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/skills/angular-developer/references/testing-fundamentals.md +65 -65
- package/skills/api-connector-builder/SKILL.md +121 -121
- package/skills/api-design/SKILL.md +524 -524
- package/skills/architecture-decision-records/SKILL.md +180 -180
- package/skills/article-writing/SKILL.md +80 -80
- package/skills/automation-audit-ops/SKILL.md +143 -143
- package/skills/autonomous-agent-harness/SKILL.md +274 -274
- package/skills/autonomous-loops/SKILL.md +611 -611
- package/skills/backend-patterns/SKILL.md +562 -562
- package/skills/benchmark/SKILL.md +95 -95
- package/skills/benchmark-methodology/SKILL.md +191 -191
- package/skills/benchmark-optimization-loop/SKILL.md +71 -71
- package/skills/blender-motion-state-inspection/SKILL.md +165 -165
- package/skills/blueprint/SKILL.md +106 -106
- package/skills/brand-discovery/SKILL.md +145 -145
- package/skills/brand-discovery/references/10_purpose-why.md +40 -40
- package/skills/brand-discovery/references/20_positioning.md +44 -44
- package/skills/brand-discovery/references/30_audience-niche.md +52 -52
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
- package/skills/brand-discovery/references/50_voice-tone.md +59 -59
- package/skills/brand-discovery/references/60_narrative-story.md +50 -50
- package/skills/brand-discovery/references/70_founder-tension.md +49 -49
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
- package/skills/brand-voice/SKILL.md +98 -98
- package/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/skills/browser-qa/SKILL.md +105 -105
- package/skills/bun-runtime/SKILL.md +85 -85
- package/skills/canary-watch/SKILL.md +108 -108
- package/skills/carrier-relationship-management/SKILL.md +212 -212
- package/skills/cisco-ios-patterns/SKILL.md +164 -164
- package/skills/ck/SKILL.md +148 -148
- package/skills/claude-devfleet/SKILL.md +112 -112
- package/skills/click-path-audit/SKILL.md +245 -245
- package/skills/clickhouse-io/SKILL.md +445 -445
- package/skills/code-tour/SKILL.md +254 -254
- package/skills/codebase-onboarding/SKILL.md +234 -234
- package/skills/codehealth-mcp/SKILL.md +167 -167
- package/skills/codeply-taste/LICENSE +21 -0
- package/skills/codeply-taste/SKILL.md +100 -0
- package/skills/codeply-taste/SOURCE.md +10 -0
- package/skills/codeply-taste/assets/base.css +100 -0
- package/skills/codeply-taste/assets/base.js +37 -0
- package/skills/codeply-taste/library/directions.json +1078 -0
- package/skills/codeply-taste/library/heroes.json +894 -0
- package/skills/codeply-taste/library/motions.json +57 -0
- package/skills/codeply-taste/library/palettes.json +1485 -0
- package/skills/codeply-taste/library/photos.json +7240 -0
- package/skills/codeply-taste/library/sections.json +4136 -0
- package/skills/codeply-taste/library/sites.json +7274 -0
- package/skills/codeply-taste/library/type.json +2024 -0
- package/skills/codeply-taste/references/colour.md +141 -0
- package/skills/codeply-taste/references/copy.md +177 -0
- package/skills/codeply-taste/references/critique.md +171 -0
- package/skills/codeply-taste/references/imagery.md +377 -0
- package/skills/codeply-taste/references/layout.md +178 -0
- package/skills/codeply-taste/references/motion.md +292 -0
- package/skills/codeply-taste/references/research.md +306 -0
- package/skills/codeply-taste/references/tells.md +115 -0
- package/skills/codeply-taste/references/type.md +170 -0
- package/skills/codeply-taste/scripts/assets.mjs +865 -0
- package/skills/codeply-taste/scripts/draw.mjs +525 -0
- package/skills/codeply-taste/scripts/inspect.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/cdp.mjs +205 -0
- package/skills/codeply-taste/scripts/lib/probe.mjs +569 -0
- package/skills/codeply-taste/scripts/lib/rules.mjs +137 -0
- package/skills/codeply-taste/scripts/lint.mjs +108 -0
- package/skills/coding-standards/SKILL.md +551 -551
- package/skills/competitive-platform-analysis/SKILL.md +214 -214
- package/skills/competitive-report-structure/SKILL.md +162 -162
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
- package/skills/config-gc/SKILL.md +120 -120
- package/skills/configure-ecc/SKILL.md +385 -385
- package/skills/connections-optimizer/SKILL.md +190 -190
- package/skills/content-engine/SKILL.md +132 -132
- package/skills/content-hash-cache-pattern/SKILL.md +162 -162
- package/skills/context-budget/SKILL.md +136 -136
- package/skills/continuous-agent-loop/SKILL.md +46 -46
- package/skills/continuous-learning/SKILL.md +132 -132
- package/skills/continuous-learning-v2/SKILL.md +361 -361
- package/skills/contract-first/SKILL.md +287 -287
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
- package/skills/cost-tracking/SKILL.md +97 -97
- package/skills/council/SKILL.md +204 -204
- package/skills/cpp-coding-standards/SKILL.md +724 -724
- package/skills/cpp-testing/SKILL.md +325 -325
- package/skills/crosspost/SKILL.md +112 -112
- package/skills/csharp-testing/SKILL.md +322 -322
- package/skills/customer-billing-ops/SKILL.md +141 -141
- package/skills/customs-trade-compliance/SKILL.md +263 -263
- package/skills/dart-flutter-patterns/SKILL.md +564 -564
- package/skills/dashboard-builder/SKILL.md +109 -109
- package/skills/data-scraper-agent/SKILL.md +765 -765
- package/skills/data-throughput-accelerator/SKILL.md +74 -74
- package/skills/database-migrations/SKILL.md +430 -430
- package/skills/deep-research/SKILL.md +160 -160
- package/skills/defi-amm-security/SKILL.md +167 -167
- package/skills/delivery-gate/SKILL.md +126 -126
- package/skills/deployment-patterns/SKILL.md +428 -428
- package/skills/design-system/SKILL.md +83 -83
- package/skills/django-celery/SKILL.md +458 -458
- package/skills/django-patterns/SKILL.md +735 -735
- package/skills/django-security/SKILL.md +644 -644
- package/skills/django-tdd/SKILL.md +730 -730
- package/skills/django-verification/SKILL.md +470 -470
- package/skills/dmux-workflows/SKILL.md +192 -192
- package/skills/docker-patterns/SKILL.md +365 -365
- package/skills/documentation-lookup/SKILL.md +91 -91
- package/skills/dotnet-patterns/SKILL.md +322 -322
- package/skills/dynamic-workflow-mode/SKILL.md +124 -124
- package/skills/e2e-testing/SKILL.md +327 -327
- package/skills/ecc-guide/SKILL.md +190 -190
- package/skills/ecc-recipes/SKILL.md +149 -149
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
- package/skills/email-ops/SKILL.md +122 -122
- package/skills/energy-procurement/SKILL.md +228 -228
- package/skills/enterprise-agent-ops/SKILL.md +51 -51
- package/skills/error-handling/SKILL.md +377 -377
- package/skills/eval-harness/SKILL.md +271 -271
- package/skills/evm-token-decimals/SKILL.md +131 -131
- package/skills/exa-search/SKILL.md +108 -108
- package/skills/fal-ai-media/SKILL.md +289 -289
- package/skills/fastapi-patterns/SKILL.md +514 -514
- package/skills/finance-billing-ops/SKILL.md +128 -128
- package/skills/flox-environments/SKILL.md +497 -497
- package/skills/flutter-dart-code-review/SKILL.md +436 -436
- package/skills/foundation-models-on-device/SKILL.md +243 -243
- package/skills/frontend-a11y/SKILL.md +446 -446
- package/skills/frontend-design-direction/SKILL.md +93 -93
- package/skills/frontend-patterns/SKILL.md +657 -657
- package/skills/frontend-slides/SKILL.md +185 -185
- package/skills/fsharp-testing/SKILL.md +281 -281
- package/skills/gan-style-harness/SKILL.md +279 -279
- package/skills/gateguard/SKILL.md +133 -133
- package/skills/generating-python-installer/SKILL.md +820 -820
- package/skills/git-workflow/SKILL.md +716 -716
- package/skills/github-ops/SKILL.md +145 -145
- package/skills/golang-patterns/SKILL.md +676 -676
- package/skills/golang-testing/SKILL.md +721 -721
- package/skills/google-workspace-ops/SKILL.md +96 -96
- package/skills/growth-log/SKILL.md +128 -128
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
- package/skills/healthcare-emr-patterns/SKILL.md +160 -160
- package/skills/healthcare-eval-harness/SKILL.md +208 -208
- package/skills/healthcare-phi-compliance/SKILL.md +146 -146
- package/skills/hermes-imports/SKILL.md +89 -89
- package/skills/hexagonal-architecture/SKILL.md +277 -277
- package/skills/hipaa-compliance/SKILL.md +79 -79
- package/skills/homelab-network-readiness/SKILL.md +170 -170
- package/skills/homelab-network-setup/SKILL.md +130 -130
- package/skills/homelab-pihole-dns/SKILL.md +275 -275
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
- package/skills/hookify-rules/SKILL.md +128 -128
- package/skills/inherit-legacy-style/SKILL.md +157 -157
- package/skills/intent-driven-development/SKILL.md +360 -360
- package/skills/inventory-demand-planning/SKILL.md +247 -247
- package/skills/investor-materials/SKILL.md +97 -97
- package/skills/investor-outreach/SKILL.md +92 -92
- package/skills/ios-icon-gen/SKILL.md +158 -158
- package/skills/iterative-retrieval/SKILL.md +212 -212
- package/skills/ito-basket-compare/SKILL.md +64 -64
- package/skills/ito-compute/SKILL.md +129 -129
- package/skills/ito-data-atlas-agent/SKILL.md +64 -64
- package/skills/ito-market-intelligence/SKILL.md +61 -61
- package/skills/ito-trade-planner/SKILL.md +68 -68
- package/skills/java-coding-standards/SKILL.md +384 -384
- package/skills/jira-integration/SKILL.md +303 -303
- package/skills/jpa-patterns/SKILL.md +152 -152
- package/skills/knowledge-ops/SKILL.md +155 -155
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
- package/skills/kotlin-patterns/SKILL.md +712 -712
- package/skills/kotlin-testing/SKILL.md +825 -825
- package/skills/kubernetes-patterns/SKILL.md +756 -756
- package/skills/laravel-patterns/SKILL.md +416 -416
- package/skills/laravel-plugin-discovery/SKILL.md +230 -230
- package/skills/laravel-security/SKILL.md +948 -948
- package/skills/laravel-tdd/SKILL.md +675 -675
- package/skills/laravel-verification/SKILL.md +180 -180
- package/skills/latency-critical-systems/SKILL.md +75 -75
- package/skills/lead-intelligence/SKILL.md +322 -322
- package/skills/liquid-glass-design/SKILL.md +279 -279
- package/skills/llm-trading-agent-security/SKILL.md +147 -147
- package/skills/logistics-exception-management/SKILL.md +222 -222
- package/skills/loop-design-check/SKILL.md +143 -143
- package/skills/mailtrap-email-integration/SKILL.md +77 -77
- package/skills/make-interfaces-feel-better/SKILL.md +152 -152
- package/skills/manim-video/SKILL.md +90 -90
- package/skills/market-research/SKILL.md +76 -76
- package/skills/marketing-campaign/SKILL.md +114 -114
- package/skills/mcp-server-patterns/SKILL.md +70 -70
- package/skills/messages-ops/SKILL.md +105 -105
- package/skills/ml-adoption-playbook/SKILL.md +57 -57
- package/skills/mle-workflow/SKILL.md +348 -348
- package/skills/motion-advanced/SKILL.md +596 -596
- package/skills/motion-foundations/SKILL.md +299 -299
- package/skills/motion-patterns/SKILL.md +434 -434
- package/skills/motion-ui/SKILL.md +576 -576
- package/skills/mysql-patterns/SKILL.md +413 -413
- package/skills/nanoclaw-repl/SKILL.md +34 -34
- package/skills/nestjs-patterns/SKILL.md +231 -231
- package/skills/netmiko-ssh-automation/SKILL.md +174 -174
- package/skills/network-bgp-diagnostics/SKILL.md +168 -168
- package/skills/network-config-validation/SKILL.md +211 -211
- package/skills/network-interface-health/SKILL.md +153 -153
- package/skills/nextjs-turbopack/SKILL.md +58 -58
- package/skills/nodejs-keccak256/SKILL.md +103 -103
- package/skills/nutrient-document-processing/SKILL.md +168 -168
- package/skills/nuxt4-patterns/SKILL.md +101 -101
- package/skills/openclaw-persona-forge/SKILL.md +289 -289
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/skills/opensource-pipeline/SKILL.md +256 -256
- package/skills/orch-add-feature/SKILL.md +45 -45
- package/skills/orch-build-mvp/SKILL.md +49 -49
- package/skills/orch-change-feature/SKILL.md +43 -43
- package/skills/orch-fix-defect/SKILL.md +43 -43
- package/skills/orch-pipeline/SKILL.md +121 -121
- package/skills/orch-refine-code/SKILL.md +44 -44
- package/skills/parallel-execution-optimizer/SKILL.md +74 -74
- package/skills/perl-patterns/SKILL.md +505 -505
- package/skills/perl-security/SKILL.md +504 -504
- package/skills/perl-testing/SKILL.md +476 -476
- package/skills/plan-canvas/SKILL.md +153 -153
- package/skills/plan-orchestrate/SKILL.md +263 -263
- package/skills/plankton-code-quality/SKILL.md +237 -237
- package/skills/postgres-patterns/SKILL.md +148 -148
- package/skills/prediction-market-oracle-research/SKILL.md +64 -64
- package/skills/prediction-market-risk-review/SKILL.md +61 -61
- package/skills/premium-web-design/SKILL.md +154 -154
- package/skills/prisma-patterns/SKILL.md +401 -401
- package/skills/product-capability/SKILL.md +142 -142
- package/skills/product-lens/SKILL.md +93 -93
- package/skills/production-audit/SKILL.md +207 -207
- package/skills/production-scheduling/SKILL.md +238 -238
- package/skills/project-flow-ops/SKILL.md +112 -112
- package/skills/prompt-optimizer/SKILL.md +398 -398
- package/skills/python-patterns/SKILL.md +751 -751
- package/skills/python-testing/SKILL.md +817 -817
- package/skills/pytorch-patterns/SKILL.md +397 -397
- package/skills/quality-nonconformance/SKILL.md +260 -260
- package/skills/quarkus-patterns/SKILL.md +723 -723
- package/skills/quarkus-security/SKILL.md +468 -468
- package/skills/quarkus-tdd/SKILL.md +812 -812
- package/skills/quarkus-verification/SKILL.md +480 -480
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
- package/skills/react-native-patterns/SKILL.md +326 -326
- package/skills/react-patterns/SKILL.md +342 -342
- package/skills/react-performance/SKILL.md +575 -575
- package/skills/react-testing/SKILL.md +424 -424
- package/skills/recsys-pipeline-architect/SKILL.md +115 -115
- package/skills/recursive-decision-ledger/SKILL.md +81 -81
- package/skills/redis-patterns/SKILL.md +404 -404
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
- package/skills/remotion-video-creation/SKILL.md +43 -43
- package/skills/repo-scan/SKILL.md +79 -79
- package/skills/research-ops/SKILL.md +113 -113
- package/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/skills/rules-distill/SKILL.md +265 -265
- package/skills/rust-patterns/SKILL.md +500 -500
- package/skills/rust-testing/SKILL.md +501 -501
- package/skills/safety-guard/SKILL.md +76 -76
- package/skills/santa-method/SKILL.md +307 -307
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
- package/skills/scientific-db-uspto-database/SKILL.md +178 -178
- package/skills/scientific-pkg-gget/SKILL.md +167 -167
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
- package/skills/search-first/SKILL.md +183 -183
- package/skills/security-bounty-hunter/SKILL.md +100 -100
- package/skills/security-review/SKILL.md +504 -504
- package/skills/security-scan/SKILL.md +166 -166
- package/skills/seo/SKILL.md +155 -155
- package/skills/skill-comply/SKILL.md +59 -59
- package/skills/skill-scout/SKILL.md +141 -141
- package/skills/skill-stocktake/SKILL.md +195 -195
- package/skills/social-graph-ranker/SKILL.md +155 -155
- package/skills/social-publisher/SKILL.md +130 -130
- package/skills/springboot-patterns/SKILL.md +315 -315
- package/skills/springboot-security/SKILL.md +273 -273
- package/skills/springboot-tdd/SKILL.md +159 -159
- package/skills/springboot-verification/SKILL.md +232 -232
- package/skills/strategic-compact/SKILL.md +142 -142
- package/skills/swift-actor-persistence/SKILL.md +144 -144
- package/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/skills/swift-protocol-di-testing/SKILL.md +191 -191
- package/skills/swiftui-patterns/SKILL.md +259 -259
- package/skills/taste/SKILL.md +264 -264
- package/skills/taste/references/genre-taxonomy.md +87 -87
- package/skills/tdd-workflow/SKILL.md +583 -583
- package/skills/team-agent-orchestration/SKILL.md +111 -111
- package/skills/team-builder/SKILL.md +169 -169
- package/skills/terminal-ops/SKILL.md +110 -110
- package/skills/tinystruct-patterns/SKILL.md +279 -279
- package/skills/tinystruct-patterns/references/architecture.md +90 -90
- package/skills/tinystruct-patterns/references/data-handling.md +60 -60
- package/skills/tinystruct-patterns/references/database.md +99 -99
- package/skills/tinystruct-patterns/references/routing.md +64 -64
- package/skills/tinystruct-patterns/references/system-usage.md +97 -97
- package/skills/tinystruct-patterns/references/testing.md +72 -72
- package/skills/token-budget-advisor/SKILL.md +134 -134
- package/skills/ui-demo/SKILL.md +466 -466
- package/skills/ui-to-vue/SKILL.md +135 -135
- package/skills/uncloud/SKILL.md +344 -344
- package/skills/unified-memory/SKILL.md +170 -170
- package/skills/unified-notifications-ops/SKILL.md +188 -188
- package/skills/verification-loop/SKILL.md +129 -129
- package/skills/video-editing/SKILL.md +311 -311
- package/skills/videodb/SKILL.md +375 -375
- package/skills/visa-doc-translate/SKILL.md +117 -117
- package/skills/vite-patterns/SKILL.md +450 -450
- package/skills/vue-patterns/SKILL.md +471 -471
- package/skills/windows-desktop-e2e/SKILL.md +888 -888
- package/skills/workspace-surface-audit/SKILL.md +126 -126
- package/skills/x-api/SKILL.md +235 -235
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const idx = JSON.parse(readFileSync(path.join(__dirname, 'index.json'), 'utf8'));
|
|
7
|
-
|
|
8
|
-
const CATEGORY_LABELS = {
|
|
9
|
-
productivity: 'Productivity',
|
|
10
|
-
finance: 'Finance',
|
|
11
|
-
shopping: 'Shopping',
|
|
12
|
-
social: 'Social',
|
|
13
|
-
travel: 'Travel',
|
|
14
|
-
food_delivery: 'Food Delivery',
|
|
15
|
-
health_fitness: 'Health & Fitness',
|
|
16
|
-
education: 'Education',
|
|
17
|
-
entertainment: 'Entertainment',
|
|
18
|
-
real_estate: 'Real Estate',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const rows = [];
|
|
22
|
-
for (const [cat, apps] of Object.entries(idx.categories)) {
|
|
23
|
-
for (const a of apps) {
|
|
24
|
-
rows.push({
|
|
25
|
-
n: a.name,
|
|
26
|
-
c: cat,
|
|
27
|
-
r: a.rating ? Math.round(a.rating * 10) / 10 : null,
|
|
28
|
-
rc: a.ratingCount || 0,
|
|
29
|
-
s: a.screenshots.length,
|
|
30
|
-
u: a.appStoreUrl,
|
|
31
|
-
i: a.icon,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
rows.sort((x, y) => y.s - x.s);
|
|
36
|
-
|
|
37
|
-
const out = { generatedAt: new Date().toISOString(), labels: CATEGORY_LABELS, rows };
|
|
38
|
-
writeFileSync(path.join(__dirname, 'table-data.json'), JSON.stringify(out));
|
|
39
|
-
console.log('rows:', rows.length);
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const idx = JSON.parse(readFileSync(path.join(__dirname, 'index.json'), 'utf8'));
|
|
7
|
+
|
|
8
|
+
const CATEGORY_LABELS = {
|
|
9
|
+
productivity: 'Productivity',
|
|
10
|
+
finance: 'Finance',
|
|
11
|
+
shopping: 'Shopping',
|
|
12
|
+
social: 'Social',
|
|
13
|
+
travel: 'Travel',
|
|
14
|
+
food_delivery: 'Food Delivery',
|
|
15
|
+
health_fitness: 'Health & Fitness',
|
|
16
|
+
education: 'Education',
|
|
17
|
+
entertainment: 'Entertainment',
|
|
18
|
+
real_estate: 'Real Estate',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const rows = [];
|
|
22
|
+
for (const [cat, apps] of Object.entries(idx.categories)) {
|
|
23
|
+
for (const a of apps) {
|
|
24
|
+
rows.push({
|
|
25
|
+
n: a.name,
|
|
26
|
+
c: cat,
|
|
27
|
+
r: a.rating ? Math.round(a.rating * 10) / 10 : null,
|
|
28
|
+
rc: a.ratingCount || 0,
|
|
29
|
+
s: a.screenshots.length,
|
|
30
|
+
u: a.appStoreUrl,
|
|
31
|
+
i: a.icon,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
rows.sort((x, y) => y.s - x.s);
|
|
36
|
+
|
|
37
|
+
const out = { generatedAt: new Date().toISOString(), labels: CATEGORY_LABELS, rows };
|
|
38
|
+
writeFileSync(path.join(__dirname, 'table-data.json'), JSON.stringify(out));
|
|
39
|
+
console.log('rows:', rows.length);
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codeply's design layer: the two tools skills/codeply-taste/SKILL.md works with.
|
|
3
|
+
*
|
|
4
|
+
* design_direction deals an art direction from the skill's research library
|
|
5
|
+
* (scripts/draw.mjs): the script chooses, not the model, so
|
|
6
|
+
* designs don't all come out as the category default.
|
|
7
|
+
* design_lint checks a finished design: a static scan for the tells the
|
|
8
|
+
* skill bans (scripts/lint.mjs), then a real render. In Craft
|
|
9
|
+
* the render happens in Craft's own off-screen browser
|
|
10
|
+
* (ctx.browser.render), measured with the skill's probe and
|
|
11
|
+
* judged with its rules; the agent also gets screenshots.
|
|
12
|
+
* In the terminal there is no browser: the scan alone runs.
|
|
13
|
+
*
|
|
14
|
+
* The scripts are plain Node with no dependencies. They run through this
|
|
15
|
+
* process's own executable (Craft's, in Node mode), so nothing extra has to be
|
|
16
|
+
* installed.
|
|
17
|
+
*/
|
|
18
|
+
import { execFile } from 'node:child_process';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
|
+
import { probe, frame } from '../skills/codeply-taste/scripts/lib/probe.mjs';
|
|
23
|
+
import { judge } from '../skills/codeply-taste/scripts/lib/rules.mjs';
|
|
24
|
+
|
|
25
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const SCRIPTS = path.join(HERE, '..', 'skills', 'codeply-taste', 'scripts');
|
|
27
|
+
|
|
28
|
+
function runScript(script, argv, cwd, timeoutMs = 30000) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
execFile(process.execPath, [path.join(SCRIPTS, script), ...argv], {
|
|
31
|
+
cwd, timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, windowsHide: true,
|
|
32
|
+
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
|
33
|
+
}, (err, stdout, stderr) => resolve({
|
|
34
|
+
code: err ? (typeof err.code === 'number' ? err.code : 1) : 0,
|
|
35
|
+
out: String(stdout || '').trim(),
|
|
36
|
+
err: String(stderr || '').trim() || (err && !stdout ? err.message : ''),
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inside the design's own folder only: a design run never reaches into anything else.
|
|
42
|
+
function insideCwd(p, cwd) {
|
|
43
|
+
const abs = path.resolve(cwd, String(p || ''));
|
|
44
|
+
const rel = path.relative(cwd, abs);
|
|
45
|
+
return rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? abs : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DIRECTION_FLAGS = ['sector', 'brief', 'tone', 'sections', 'have', 'keep', 'avoid', 'direction', 'redraw', 'list', 'site', 'seed'];
|
|
49
|
+
|
|
50
|
+
export async function design_direction(args, ctx) {
|
|
51
|
+
const argv = [];
|
|
52
|
+
for (const k of DIRECTION_FLAGS) {
|
|
53
|
+
const v = args[k] == null ? '' : String(args[k]).trim();
|
|
54
|
+
if (v) argv.push(`--${k}`, v);
|
|
55
|
+
}
|
|
56
|
+
if (/^(1|true|yes|3)$/i.test(String(args.options || '').trim())) argv.push('--options', '3');
|
|
57
|
+
if (/^(1|true|yes)$/i.test(String(args.show || '').trim())) argv.push('--show');
|
|
58
|
+
if (!argv.length) {
|
|
59
|
+
return { ok: false, output: 'design_direction needs a <brief> and <sector> (or <direction>, <redraw>, <show>, <list>). Sectors: <list>sectors</list>.' };
|
|
60
|
+
}
|
|
61
|
+
argv.push('--out', path.join(ctx.cwd, '.taste-skill'));
|
|
62
|
+
const r = await runScript('draw.mjs', argv, ctx.cwd);
|
|
63
|
+
if (r.code !== 0 && !r.out) return { ok: false, output: `design_direction failed: ${r.err || 'no output'}` };
|
|
64
|
+
// The sheet suggests asset commands for a terminal; in Codeply, pictures come from image_search / fetch_image.
|
|
65
|
+
// Its wording is for a terminal: say it in this tool's parameters.
|
|
66
|
+
const text = r.out
|
|
67
|
+
.replace(/node \S*assets\.mjs/g, '(assets: use image_search, then fetch_image into assets/)')
|
|
68
|
+
.replace(/the same command plus `?--direction <id>`?(?: \(and without --options\))?/g, 'design_direction with direction=<id>')
|
|
69
|
+
.replace(/`?--(direction|redraw|options|keep|avoid|have|tone|sections|show)\b`?/g, '$1=')
|
|
70
|
+
.replace(/ ?\(SKILL\.md steps? [\d, and]+\)/g, '');
|
|
71
|
+
return { ok: true, output: text.slice(0, 24000), meta: { label: args.direction || args.redraw || args.sector || 'direction' } };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// What each kind of design is judged on. Rules about a web page's shape (a hero, a first
|
|
75
|
+
// action, footage, pictures, motion while scrolling) don't apply to a slide or a document.
|
|
76
|
+
const PAGE_RULES = ['no-hero', 'first-action', 'few-pictures', 'no-real-pictures', 'no-footage', 'still-page', 'empty-screens', 'long-pin', 'pinned-over-picture'];
|
|
77
|
+
const KIND = {
|
|
78
|
+
website: { widths: [1440, 390], allow: [] },
|
|
79
|
+
app: { widths: [390], allow: [...PAGE_RULES, 'ui-wrapper', 'card-rows', 'pill-nav', 'icon-cards'] },
|
|
80
|
+
slides: { widths: [1280], height: 720, allow: [...PAGE_RULES, 'side-scroll', 'all-centred', 'measure'] },
|
|
81
|
+
doc: { widths: [816], height: 1056, allow: [...PAGE_RULES, 'all-centred'] },
|
|
82
|
+
poster: { widths: [1080], height: 1350, allow: [...PAGE_RULES, 'all-centred', 'side-scroll'] },
|
|
83
|
+
social: { widths: [1080], height: 1080, allow: [...PAGE_RULES, 'all-centred', 'side-scroll'] },
|
|
84
|
+
logo: { widths: [1200], height: 800, allow: [...PAGE_RULES, 'all-centred', 'font-count', 'size-count', 'body-size', 'small-text'] },
|
|
85
|
+
};
|
|
86
|
+
const MOBILE_H = 844;
|
|
87
|
+
// A check takes a while and a model left to itself keeps checking: one when the
|
|
88
|
+
// design is complete, one after fixing, and a third at most, per request.
|
|
89
|
+
const MAX_CHECKS = 3;
|
|
90
|
+
|
|
91
|
+
export async function design_lint(args, ctx) {
|
|
92
|
+
ctx.designChecks = (ctx.designChecks || 0) + 1;
|
|
93
|
+
if (ctx.designChecks > MAX_CHECKS) {
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
output: `Not checked: that would be check ${ctx.designChecks} for this request, and ${MAX_CHECKS} is the most. Stop checking and write your summary now. If something still looks off, say what in one line; the person can ask for another pass.`,
|
|
97
|
+
meta: { label: 'skipped, checked enough' },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const file = insideCwd(args.path || 'index.html', ctx.cwd);
|
|
101
|
+
if (!file || !fs.existsSync(file)) return { ok: false, output: `design_lint: no such file in this design: ${args.path || 'index.html'}.` };
|
|
102
|
+
const kind = KIND[String(args.kind || 'website').trim().toLowerCase()] ? String(args.kind || 'website').trim().toLowerCase() : 'website';
|
|
103
|
+
const spec = KIND[kind];
|
|
104
|
+
const widths = String(args.widths || '').split(',').map((s) => parseInt(s, 10)).filter((n) => n >= 200 && n <= 2560);
|
|
105
|
+
const useWidths = widths.length ? widths.slice(0, 3) : spec.widths;
|
|
106
|
+
|
|
107
|
+
// Allowances: the kind's, the drawn direction's (draw.mjs grants some), and any the agent names with a reason.
|
|
108
|
+
let allow = [...spec.allow];
|
|
109
|
+
try { allow.push(...(JSON.parse(fs.readFileSync(path.join(ctx.cwd, '.taste-skill', 'direction.json'), 'utf8')).allow || [])); } catch {}
|
|
110
|
+
allow.push(...String(args.allow || '').split(',').map((s) => s.trim()).filter(Boolean));
|
|
111
|
+
allow = [...new Set(allow)];
|
|
112
|
+
|
|
113
|
+
const findings = [];
|
|
114
|
+
const scan = await runScript('lint.mjs', [file, '--json'], ctx.cwd);
|
|
115
|
+
try {
|
|
116
|
+
for (const f of JSON.parse(scan.out).findings || []) if (!allow.includes(f.id)) findings.push({ ...f, where: `${path.basename(f.file || file)}:${f.line || '?'}` });
|
|
117
|
+
} catch { if (scan.err) findings.push({ level: 'WARN', id: 'lint', message: `The static scan did not run: ${scan.err.slice(0, 200)}` }); }
|
|
118
|
+
|
|
119
|
+
const render = ctx.browser && typeof ctx.browser.render === 'function' ? ctx.browser.render : null;
|
|
120
|
+
const images = [];
|
|
121
|
+
const measured = [];
|
|
122
|
+
if (render) {
|
|
123
|
+
for (const w of useWidths) {
|
|
124
|
+
const h = spec.height || (w < 600 ? MOBILE_H : Math.round(w * 0.625));
|
|
125
|
+
const r = await render(pathToFileURL(file).href, { width: w, height: h, probe: probe.toString(), frame: frame.toString(), screens: kind === 'website' && w >= 1000 ? 4 : kind === 'doc' || kind === 'slides' ? 3 : 1 });
|
|
126
|
+
if (!r || !r.ok) { findings.push({ level: 'FAIL', id: 'render', width: w, message: `The design did not render at ${w}px: ${(r && r.error) || 'unknown error'}` }); continue; }
|
|
127
|
+
if (r.metrics && !r.metrics.error) {
|
|
128
|
+
for (const f of judge(r.metrics, { width: w, allow })) findings.push(f);
|
|
129
|
+
const m = r.metrics;
|
|
130
|
+
measured.push(`@${w}px: ${m.page ? `${m.page.screens} screen(s), ${m.page.words} words, ${m.page.img} images` : ''}${m.type ? ` · type ${m.type.families.filter((f) => f.share >= 0.02).map((f) => f.family).join(', ')}` : ''}${m.hero ? ` · headline ${m.hero.lines} line(s) at ${m.hero.size}px` : ''}`);
|
|
131
|
+
} else if (r.metrics && r.metrics.error) findings.push({ level: 'WARN', id: 'probe', width: w, message: `Could not measure at ${w}px: ${r.metrics.error}` });
|
|
132
|
+
const covered = [...new Map((r.frames || []).filter(Boolean).flatMap((f, i) => (f.covered || []).map((c) => [c.text, { ...c, screen: i + 1 }]))).values()];
|
|
133
|
+
if (covered.length && !allow.includes('covered-text')) findings.push({ level: 'FAIL', id: 'covered-text', width: w, message: 'Large text lies under another element: ' + covered.slice(0, 3).map((c) => `"${c.text}" (screen ${c.screen})`).join('; '), fix: 'Nothing may sit on top of a headline. Move the picture or raise the text above it with a ground.' });
|
|
134
|
+
for (const e of (r.consoleErrors || []).slice(0, 2)) findings.push({ level: 'WARN', id: 'console', width: w, message: `Console error: ${String(e).slice(0, 200)}` });
|
|
135
|
+
for (const img of r.images || []) if (images.length < 4) images.push(img);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The same rule at several widths: keep the first, note the others.
|
|
140
|
+
const merged = [];
|
|
141
|
+
for (const f of findings) {
|
|
142
|
+
const same = merged.find((x) => x.id === f.id && x.level === f.level && f.id !== 'headline-lines');
|
|
143
|
+
if (same && f.width) { same.alsoAt = [...(same.alsoAt || []), f.width]; continue; }
|
|
144
|
+
merged.push(f);
|
|
145
|
+
}
|
|
146
|
+
const fails = merged.filter((f) => f.level === 'FAIL');
|
|
147
|
+
const warns = merged.filter((f) => f.level === 'WARN');
|
|
148
|
+
const lines = [`design_lint ${path.relative(ctx.cwd, file).split(path.sep).join('/')} as ${kind}${render ? ` at ${useWidths.join(', ')}px` : ' (static scan only: no browser here)'}`];
|
|
149
|
+
if (measured.length) lines.push(...measured);
|
|
150
|
+
for (const [title, list] of [['FAIL (fix every one)', fails], ['WARN (optional: fix the quick ones in the same edit as the FAILs, leave the rest; never check again just for a WARN)', warns]]) {
|
|
151
|
+
if (!list.length) continue;
|
|
152
|
+
lines.push('', title);
|
|
153
|
+
for (const f of list.slice(0, 20)) lines.push(`- [${f.id}]${f.width ? ` @${f.width}px` : ''}${f.alsoAt ? ` (+${f.alsoAt.join(', ')})` : ''}${f.where ? ` ${f.where}` : ''} ${f.message}${f.fix ? `\n fix: ${f.fix}` : ''}`);
|
|
154
|
+
}
|
|
155
|
+
if (images.length) lines.push('', `${images.length} screenshot(s) attached.`);
|
|
156
|
+
// What to do next, so a clean result ends the checking instead of starting more.
|
|
157
|
+
if (!fails.length) {
|
|
158
|
+
lines.push('', `It passes (check ${ctx.designChecks} of ${MAX_CHECKS}). You are done checking for this request: glance at the screenshots, fix anything plainly broken in one edit without checking again, then write your summary.`);
|
|
159
|
+
} else if (ctx.designChecks >= MAX_CHECKS) {
|
|
160
|
+
lines.push('', `That was the last check for this request (${ctx.designChecks} of ${MAX_CHECKS}). Fix the FAILs in one edit, then write your summary and say which ones you fixed.`);
|
|
161
|
+
} else {
|
|
162
|
+
lines.push('', `Check ${ctx.designChecks} of ${MAX_CHECKS}. Fix all the FAILs in one or two edits, then check once more.`);
|
|
163
|
+
}
|
|
164
|
+
// The rules' fixes name the skill's terminal scripts; here, pictures come from the tools.
|
|
165
|
+
const output = lines.join('\n')
|
|
166
|
+
.replace(/scripts\/assets\.mjs(?: --video)?/g, 'image_search (then fetch_image into assets/)')
|
|
167
|
+
.replace(/ ?\(SKILL\.md step \d+[^)]*\)/g, '');
|
|
168
|
+
return {
|
|
169
|
+
ok: true,
|
|
170
|
+
output: output.slice(0, 20000),
|
|
171
|
+
meta: { label: `${kind}${fails.length ? `, ${fails.length} to fix` : ', clean'}`, count: fails.length, ...(images.length ? { imageDataUrls: images } : {}) },
|
|
172
|
+
};
|
|
173
|
+
}
|
package/lib/diagnostics.js
CHANGED
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Quick syntax checks on a file the agent just wrote or edited, so a broken
|
|
3
|
-
* change is reported in the same step instead of surfacing three steps later
|
|
4
|
-
* (or never). Syntax only, and fast: nothing here type-checks a whole project
|
|
5
|
-
* or needs a language server. Idea from opencode, which appends LSP errors to
|
|
6
|
-
* edit results.
|
|
7
|
-
*
|
|
8
|
-
* checkFile() never throws; when a checker is missing or unsure it stays quiet.
|
|
9
|
-
*/
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { execFile } = require('child_process');
|
|
13
|
-
|
|
14
|
-
const TIMEOUT_MS = 8000;
|
|
15
|
-
const MAX_PROBLEMS = 10;
|
|
16
|
-
|
|
17
|
-
function run(cmd, args, opts = {}) {
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
execFile(cmd, args, { timeout: TIMEOUT_MS, windowsHide: true, maxBuffer: 1024 * 1024, ...opts }, (err, stdout, stderr) => {
|
|
20
|
-
resolve({ code: err ? (typeof err.code === 'number' ? err.code : 1) : 0, out: `${stdout || ''}${stderr || ''}`, missing: err && err.code === 'ENOENT' });
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ── JavaScript ──
|
|
26
|
-
async function checkJs(file) {
|
|
27
|
-
const r = await run(process.execPath, ['--check', file], { env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } });
|
|
28
|
-
if (r.code === 0) return [];
|
|
29
|
-
const text = r.out.trim();
|
|
30
|
-
// JSX in a .js file (common in React projects) is not plain JS; node can't judge it.
|
|
31
|
-
if (/Unexpected token '<'|Unexpected token <|JSX/.test(text)) return [];
|
|
32
|
-
const loc = text.match(/:(\d+)\r?\n/);
|
|
33
|
-
const msg = (text.match(/^(SyntaxError: .+)$/m) || [null, text.split('\n').find((l) => l.trim()) || 'syntax error'])[1];
|
|
34
|
-
return [`${loc ? `line ${loc[1]}: ` : ''}${msg}`];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ── JSON ──
|
|
38
|
-
const JSONC_NAMES = /^(tsconfig.*|jsconfig.*|\.eslintrc|devcontainer|settings|launch|tasks|extensions)\.json$/i;
|
|
39
|
-
function checkJson(file, text) {
|
|
40
|
-
if (JSONC_NAMES.test(path.basename(file)) || /[\\/]\.vscode[\\/]/.test(file)) return [];
|
|
41
|
-
try { JSON.parse(text.replace(/^\uFEFF/, '')); return []; }
|
|
42
|
-
catch (e) {
|
|
43
|
-
const pos = Number((e.message.match(/position (\d+)/) || [])[1]);
|
|
44
|
-
const line = Number.isFinite(pos) ? text.slice(0, pos).split('\n').length : null;
|
|
45
|
-
return [`${line ? `line ${line}: ` : ''}invalid JSON (${e.message.replace(/ in JSON at position \d+.*$/, '')})`];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ── TypeScript / TSX, through the project's own compiler, syntax only ──
|
|
50
|
-
function findUp(start, rel, stop) {
|
|
51
|
-
let dir = start;
|
|
52
|
-
while (true) {
|
|
53
|
-
const p = path.join(dir, rel);
|
|
54
|
-
if (fs.existsSync(p)) return p;
|
|
55
|
-
const up = path.dirname(dir);
|
|
56
|
-
if (up === dir || (stop && dir === stop)) return null;
|
|
57
|
-
dir = up;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// ── Real type checking (semantic), in a warm worker per project ──
|
|
61
|
-
const { fork } = require('child_process');
|
|
62
|
-
const workers = new Map(); // project root -> { proc, pending, nextId, ready }
|
|
63
|
-
const FIRST_CHECK_MS = 25000; // a big project's first build can take a while
|
|
64
|
-
const CHECK_MS = 10000;
|
|
65
|
-
|
|
66
|
-
function typeWorker(projectRoot, tsDir) {
|
|
67
|
-
let w = workers.get(projectRoot);
|
|
68
|
-
if (w && !w.dead) return w;
|
|
69
|
-
const proc = fork(path.join(__dirname, 'typecheck-worker.js'), [projectRoot, tsDir], {
|
|
70
|
-
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
|
71
|
-
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
72
|
-
windowsHide: true,
|
|
73
|
-
});
|
|
74
|
-
w = { proc, pending: new Map(), nextId: 1, checks: 0, dead: false };
|
|
75
|
-
proc.on('message', (m) => {
|
|
76
|
-
const p = m && m.id != null && w.pending.get(m.id);
|
|
77
|
-
if (p) { w.pending.delete(m.id); p(m); }
|
|
78
|
-
});
|
|
79
|
-
proc.on('exit', () => { w.dead = true; for (const [, p] of w.pending) p({ error: 'worker exited' }); w.pending.clear(); });
|
|
80
|
-
proc.unref();
|
|
81
|
-
workers.set(projectRoot, w);
|
|
82
|
-
return w;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function typeCheck(file, projectRoot, tsDir) {
|
|
86
|
-
const w = typeWorker(projectRoot, tsDir);
|
|
87
|
-
const timeout = w.checks++ === 0 ? FIRST_CHECK_MS : CHECK_MS;
|
|
88
|
-
return new Promise((resolve) => {
|
|
89
|
-
const id = w.nextId++;
|
|
90
|
-
const timer = setTimeout(() => { w.pending.delete(id); resolve(null); }, timeout);
|
|
91
|
-
w.pending.set(id, (m) => { clearTimeout(timer); resolve(m.error ? null : m.problems); });
|
|
92
|
-
try { w.proc.send({ id, file }); } catch { clearTimeout(timer); resolve(null); }
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
process.on('exit', () => { for (const [, w] of workers) try { w.proc.kill(); } catch {} });
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Code navigation through the project's own TypeScript (works for .ts, .tsx, .js, .jsx).
|
|
99
|
-
* @param {{op:'definition'|'references'|'implementation'|'hover'|'documentSymbol'|'workspaceSymbol', file?:string, line?:number, character?:number, symbol?:string, query?:string}} req
|
|
100
|
-
* @returns {Promise<{ok:boolean, text:string}>}
|
|
101
|
-
*/
|
|
102
|
-
async function navigate(req, cwd) {
|
|
103
|
-
const start = req.file ? path.dirname(req.file) : cwd;
|
|
104
|
-
const tsDir = findUp(start, path.join('node_modules', 'typescript'), path.parse(cwd).root);
|
|
105
|
-
if (!tsDir) return { ok: false, text: 'Code navigation needs TypeScript installed in the project (run npm install typescript). Use search instead.' };
|
|
106
|
-
const cfg = findUp(start, 'tsconfig.json', path.parse(cwd).root) || findUp(start, 'jsconfig.json', path.parse(cwd).root);
|
|
107
|
-
const projectRoot = cfg ? path.dirname(cfg) : cwd;
|
|
108
|
-
const w = typeWorker(projectRoot, tsDir);
|
|
109
|
-
const timeout = w.checks++ === 0 ? FIRST_CHECK_MS : CHECK_MS;
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
const id = w.nextId++;
|
|
112
|
-
const timer = setTimeout(() => { w.pending.delete(id); resolve({ ok: false, text: 'Code navigation timed out (the project is still loading). Try again, or use search.' }); }, timeout + 10000);
|
|
113
|
-
w.pending.set(id, (m) => { clearTimeout(timer); resolve(m.error ? { ok: false, text: `Code navigation failed: ${m.error}` } : { ok: true, text: m.text }); });
|
|
114
|
-
try { w.proc.send({ id, ...req }); } catch { clearTimeout(timer); resolve({ ok: false, text: 'Code navigation is unavailable.' }); }
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function checkTs(file, text, cwd) {
|
|
119
|
-
const tsDir = findUp(path.dirname(file), path.join('node_modules', 'typescript'), path.parse(cwd).root);
|
|
120
|
-
if (!tsDir) return [];
|
|
121
|
-
// With a tsconfig/jsconfig the project is set up for type checking: ask
|
|
122
|
-
// the worker for real (semantic) errors. It falls back to the syntax-only
|
|
123
|
-
// pass below when it's slow or unavailable.
|
|
124
|
-
const cfg = findUp(path.dirname(file), 'tsconfig.json', path.parse(cwd).root) || findUp(path.dirname(file), 'jsconfig.json', path.parse(cwd).root);
|
|
125
|
-
if (cfg) {
|
|
126
|
-
const problems = await typeCheck(file, path.dirname(cfg), tsDir);
|
|
127
|
-
if (problems) return problems;
|
|
128
|
-
}
|
|
129
|
-
let ts;
|
|
130
|
-
try { ts = require(tsDir); } catch { return []; }
|
|
131
|
-
const out = ts.transpileModule(text, {
|
|
132
|
-
fileName: file,
|
|
133
|
-
reportDiagnostics: true,
|
|
134
|
-
compilerOptions: { jsx: file.endsWith('x') ? ts.JsxEmit.Preserve : undefined, target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext },
|
|
135
|
-
});
|
|
136
|
-
return (out.diagnostics || []).filter((d) => d.category === ts.DiagnosticCategory.Error).map((d) => {
|
|
137
|
-
const line = d.file && d.start != null ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null;
|
|
138
|
-
return `${line ? `line ${line}: ` : ''}${ts.flattenDiagnosticMessageText(d.messageText, ' ')}`;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ── CSS: unbalanced braces are the classic broken-edit symptom ──
|
|
143
|
-
function checkCss(text) {
|
|
144
|
-
const clean = text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/g, '""');
|
|
145
|
-
let depth = 0, line = 1;
|
|
146
|
-
for (const ch of clean) {
|
|
147
|
-
if (ch === '\n') line++;
|
|
148
|
-
else if (ch === '{') depth++;
|
|
149
|
-
else if (ch === '}' && --depth < 0) return [`line ${line}: a "}" with no matching "{"`];
|
|
150
|
-
}
|
|
151
|
-
return depth > 0 ? [`${depth} "{" never closed (end of file)`] : [];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── Python ──
|
|
155
|
-
let pythonCmd; // undefined = not probed yet, null = none found
|
|
156
|
-
async function checkPy(file) {
|
|
157
|
-
if (pythonCmd === undefined) {
|
|
158
|
-
pythonCmd = null;
|
|
159
|
-
for (const c of process.platform === 'win32' ? ['py', 'python', 'python3'] : ['python3', 'python']) {
|
|
160
|
-
const r = await run(c, ['--version']);
|
|
161
|
-
if (r.code === 0 && /Python 3/.test(r.out)) { pythonCmd = c; break; }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
if (!pythonCmd) return [];
|
|
165
|
-
const code = 'import sys,ast\nsrc=open(sys.argv[1],encoding="utf-8").read()\ntry:\n ast.parse(src)\nexcept SyntaxError as e:\n print(f"line {e.lineno}: SyntaxError: {e.msg}");sys.exit(1)';
|
|
166
|
-
const r = await run(pythonCmd, ['-c', code, file]);
|
|
167
|
-
return r.code === 0 ? [] : [r.out.trim().split('\n').pop()];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* @param {string} abs absolute path of the file that just changed
|
|
172
|
-
* @param {string} cwd project root
|
|
173
|
-
* @returns {Promise<string[]>} problems, empty when clean or not checkable
|
|
174
|
-
*/
|
|
175
|
-
async function checkFile(abs, cwd) {
|
|
176
|
-
try {
|
|
177
|
-
const ext = path.extname(abs).toLowerCase();
|
|
178
|
-
if (!['.js', '.cjs', '.mjs', '.json', '.ts', '.tsx', '.mts', '.cts', '.jsx', '.css', '.py'].includes(ext)) return [];
|
|
179
|
-
const stat = fs.statSync(abs);
|
|
180
|
-
if (stat.size > 2_000_000) return [];
|
|
181
|
-
const text = fs.readFileSync(abs, 'utf8');
|
|
182
|
-
let problems = [];
|
|
183
|
-
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') problems = await checkJs(abs);
|
|
184
|
-
else if (ext === '.json') problems = checkJson(abs, text);
|
|
185
|
-
else if (ext === '.jsx' || ext.startsWith('.ts') || ext === '.mts' || ext === '.cts') problems = await checkTs(abs, text, cwd);
|
|
186
|
-
else if (ext === '.css') problems = checkCss(text);
|
|
187
|
-
else if (ext === '.py') problems = await checkPy(abs);
|
|
188
|
-
return problems.filter(Boolean).slice(0, MAX_PROBLEMS);
|
|
189
|
-
} catch { return []; }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
module.exports = { checkFile, navigate };
|
|
1
|
+
/**
|
|
2
|
+
* Quick syntax checks on a file the agent just wrote or edited, so a broken
|
|
3
|
+
* change is reported in the same step instead of surfacing three steps later
|
|
4
|
+
* (or never). Syntax only, and fast: nothing here type-checks a whole project
|
|
5
|
+
* or needs a language server. Idea from opencode, which appends LSP errors to
|
|
6
|
+
* edit results.
|
|
7
|
+
*
|
|
8
|
+
* checkFile() never throws; when a checker is missing or unsure it stays quiet.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { execFile } = require('child_process');
|
|
13
|
+
|
|
14
|
+
const TIMEOUT_MS = 8000;
|
|
15
|
+
const MAX_PROBLEMS = 10;
|
|
16
|
+
|
|
17
|
+
function run(cmd, args, opts = {}) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
execFile(cmd, args, { timeout: TIMEOUT_MS, windowsHide: true, maxBuffer: 1024 * 1024, ...opts }, (err, stdout, stderr) => {
|
|
20
|
+
resolve({ code: err ? (typeof err.code === 'number' ? err.code : 1) : 0, out: `${stdout || ''}${stderr || ''}`, missing: err && err.code === 'ENOENT' });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── JavaScript ──
|
|
26
|
+
async function checkJs(file) {
|
|
27
|
+
const r = await run(process.execPath, ['--check', file], { env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } });
|
|
28
|
+
if (r.code === 0) return [];
|
|
29
|
+
const text = r.out.trim();
|
|
30
|
+
// JSX in a .js file (common in React projects) is not plain JS; node can't judge it.
|
|
31
|
+
if (/Unexpected token '<'|Unexpected token <|JSX/.test(text)) return [];
|
|
32
|
+
const loc = text.match(/:(\d+)\r?\n/);
|
|
33
|
+
const msg = (text.match(/^(SyntaxError: .+)$/m) || [null, text.split('\n').find((l) => l.trim()) || 'syntax error'])[1];
|
|
34
|
+
return [`${loc ? `line ${loc[1]}: ` : ''}${msg}`];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── JSON ──
|
|
38
|
+
const JSONC_NAMES = /^(tsconfig.*|jsconfig.*|\.eslintrc|devcontainer|settings|launch|tasks|extensions)\.json$/i;
|
|
39
|
+
function checkJson(file, text) {
|
|
40
|
+
if (JSONC_NAMES.test(path.basename(file)) || /[\\/]\.vscode[\\/]/.test(file)) return [];
|
|
41
|
+
try { JSON.parse(text.replace(/^\uFEFF/, '')); return []; }
|
|
42
|
+
catch (e) {
|
|
43
|
+
const pos = Number((e.message.match(/position (\d+)/) || [])[1]);
|
|
44
|
+
const line = Number.isFinite(pos) ? text.slice(0, pos).split('\n').length : null;
|
|
45
|
+
return [`${line ? `line ${line}: ` : ''}invalid JSON (${e.message.replace(/ in JSON at position \d+.*$/, '')})`];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── TypeScript / TSX, through the project's own compiler, syntax only ──
|
|
50
|
+
function findUp(start, rel, stop) {
|
|
51
|
+
let dir = start;
|
|
52
|
+
while (true) {
|
|
53
|
+
const p = path.join(dir, rel);
|
|
54
|
+
if (fs.existsSync(p)) return p;
|
|
55
|
+
const up = path.dirname(dir);
|
|
56
|
+
if (up === dir || (stop && dir === stop)) return null;
|
|
57
|
+
dir = up;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Real type checking (semantic), in a warm worker per project ──
|
|
61
|
+
const { fork } = require('child_process');
|
|
62
|
+
const workers = new Map(); // project root -> { proc, pending, nextId, ready }
|
|
63
|
+
const FIRST_CHECK_MS = 25000; // a big project's first build can take a while
|
|
64
|
+
const CHECK_MS = 10000;
|
|
65
|
+
|
|
66
|
+
function typeWorker(projectRoot, tsDir) {
|
|
67
|
+
let w = workers.get(projectRoot);
|
|
68
|
+
if (w && !w.dead) return w;
|
|
69
|
+
const proc = fork(path.join(__dirname, 'typecheck-worker.js'), [projectRoot, tsDir], {
|
|
70
|
+
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
|
|
71
|
+
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
72
|
+
windowsHide: true,
|
|
73
|
+
});
|
|
74
|
+
w = { proc, pending: new Map(), nextId: 1, checks: 0, dead: false };
|
|
75
|
+
proc.on('message', (m) => {
|
|
76
|
+
const p = m && m.id != null && w.pending.get(m.id);
|
|
77
|
+
if (p) { w.pending.delete(m.id); p(m); }
|
|
78
|
+
});
|
|
79
|
+
proc.on('exit', () => { w.dead = true; for (const [, p] of w.pending) p({ error: 'worker exited' }); w.pending.clear(); });
|
|
80
|
+
proc.unref();
|
|
81
|
+
workers.set(projectRoot, w);
|
|
82
|
+
return w;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function typeCheck(file, projectRoot, tsDir) {
|
|
86
|
+
const w = typeWorker(projectRoot, tsDir);
|
|
87
|
+
const timeout = w.checks++ === 0 ? FIRST_CHECK_MS : CHECK_MS;
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const id = w.nextId++;
|
|
90
|
+
const timer = setTimeout(() => { w.pending.delete(id); resolve(null); }, timeout);
|
|
91
|
+
w.pending.set(id, (m) => { clearTimeout(timer); resolve(m.error ? null : m.problems); });
|
|
92
|
+
try { w.proc.send({ id, file }); } catch { clearTimeout(timer); resolve(null); }
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
process.on('exit', () => { for (const [, w] of workers) try { w.proc.kill(); } catch {} });
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Code navigation through the project's own TypeScript (works for .ts, .tsx, .js, .jsx).
|
|
99
|
+
* @param {{op:'definition'|'references'|'implementation'|'hover'|'documentSymbol'|'workspaceSymbol', file?:string, line?:number, character?:number, symbol?:string, query?:string}} req
|
|
100
|
+
* @returns {Promise<{ok:boolean, text:string}>}
|
|
101
|
+
*/
|
|
102
|
+
async function navigate(req, cwd) {
|
|
103
|
+
const start = req.file ? path.dirname(req.file) : cwd;
|
|
104
|
+
const tsDir = findUp(start, path.join('node_modules', 'typescript'), path.parse(cwd).root);
|
|
105
|
+
if (!tsDir) return { ok: false, text: 'Code navigation needs TypeScript installed in the project (run npm install typescript). Use search instead.' };
|
|
106
|
+
const cfg = findUp(start, 'tsconfig.json', path.parse(cwd).root) || findUp(start, 'jsconfig.json', path.parse(cwd).root);
|
|
107
|
+
const projectRoot = cfg ? path.dirname(cfg) : cwd;
|
|
108
|
+
const w = typeWorker(projectRoot, tsDir);
|
|
109
|
+
const timeout = w.checks++ === 0 ? FIRST_CHECK_MS : CHECK_MS;
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
const id = w.nextId++;
|
|
112
|
+
const timer = setTimeout(() => { w.pending.delete(id); resolve({ ok: false, text: 'Code navigation timed out (the project is still loading). Try again, or use search.' }); }, timeout + 10000);
|
|
113
|
+
w.pending.set(id, (m) => { clearTimeout(timer); resolve(m.error ? { ok: false, text: `Code navigation failed: ${m.error}` } : { ok: true, text: m.text }); });
|
|
114
|
+
try { w.proc.send({ id, ...req }); } catch { clearTimeout(timer); resolve({ ok: false, text: 'Code navigation is unavailable.' }); }
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function checkTs(file, text, cwd) {
|
|
119
|
+
const tsDir = findUp(path.dirname(file), path.join('node_modules', 'typescript'), path.parse(cwd).root);
|
|
120
|
+
if (!tsDir) return [];
|
|
121
|
+
// With a tsconfig/jsconfig the project is set up for type checking: ask
|
|
122
|
+
// the worker for real (semantic) errors. It falls back to the syntax-only
|
|
123
|
+
// pass below when it's slow or unavailable.
|
|
124
|
+
const cfg = findUp(path.dirname(file), 'tsconfig.json', path.parse(cwd).root) || findUp(path.dirname(file), 'jsconfig.json', path.parse(cwd).root);
|
|
125
|
+
if (cfg) {
|
|
126
|
+
const problems = await typeCheck(file, path.dirname(cfg), tsDir);
|
|
127
|
+
if (problems) return problems;
|
|
128
|
+
}
|
|
129
|
+
let ts;
|
|
130
|
+
try { ts = require(tsDir); } catch { return []; }
|
|
131
|
+
const out = ts.transpileModule(text, {
|
|
132
|
+
fileName: file,
|
|
133
|
+
reportDiagnostics: true,
|
|
134
|
+
compilerOptions: { jsx: file.endsWith('x') ? ts.JsxEmit.Preserve : undefined, target: ts.ScriptTarget.ESNext, module: ts.ModuleKind.ESNext },
|
|
135
|
+
});
|
|
136
|
+
return (out.diagnostics || []).filter((d) => d.category === ts.DiagnosticCategory.Error).map((d) => {
|
|
137
|
+
const line = d.file && d.start != null ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null;
|
|
138
|
+
return `${line ? `line ${line}: ` : ''}${ts.flattenDiagnosticMessageText(d.messageText, ' ')}`;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── CSS: unbalanced braces are the classic broken-edit symptom ──
|
|
143
|
+
function checkCss(text) {
|
|
144
|
+
const clean = text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'/g, '""');
|
|
145
|
+
let depth = 0, line = 1;
|
|
146
|
+
for (const ch of clean) {
|
|
147
|
+
if (ch === '\n') line++;
|
|
148
|
+
else if (ch === '{') depth++;
|
|
149
|
+
else if (ch === '}' && --depth < 0) return [`line ${line}: a "}" with no matching "{"`];
|
|
150
|
+
}
|
|
151
|
+
return depth > 0 ? [`${depth} "{" never closed (end of file)`] : [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Python ──
|
|
155
|
+
let pythonCmd; // undefined = not probed yet, null = none found
|
|
156
|
+
async function checkPy(file) {
|
|
157
|
+
if (pythonCmd === undefined) {
|
|
158
|
+
pythonCmd = null;
|
|
159
|
+
for (const c of process.platform === 'win32' ? ['py', 'python', 'python3'] : ['python3', 'python']) {
|
|
160
|
+
const r = await run(c, ['--version']);
|
|
161
|
+
if (r.code === 0 && /Python 3/.test(r.out)) { pythonCmd = c; break; }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (!pythonCmd) return [];
|
|
165
|
+
const code = 'import sys,ast\nsrc=open(sys.argv[1],encoding="utf-8").read()\ntry:\n ast.parse(src)\nexcept SyntaxError as e:\n print(f"line {e.lineno}: SyntaxError: {e.msg}");sys.exit(1)';
|
|
166
|
+
const r = await run(pythonCmd, ['-c', code, file]);
|
|
167
|
+
return r.code === 0 ? [] : [r.out.trim().split('\n').pop()];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} abs absolute path of the file that just changed
|
|
172
|
+
* @param {string} cwd project root
|
|
173
|
+
* @returns {Promise<string[]>} problems, empty when clean or not checkable
|
|
174
|
+
*/
|
|
175
|
+
async function checkFile(abs, cwd) {
|
|
176
|
+
try {
|
|
177
|
+
const ext = path.extname(abs).toLowerCase();
|
|
178
|
+
if (!['.js', '.cjs', '.mjs', '.json', '.ts', '.tsx', '.mts', '.cts', '.jsx', '.css', '.py'].includes(ext)) return [];
|
|
179
|
+
const stat = fs.statSync(abs);
|
|
180
|
+
if (stat.size > 2_000_000) return [];
|
|
181
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
182
|
+
let problems = [];
|
|
183
|
+
if (ext === '.js' || ext === '.cjs' || ext === '.mjs') problems = await checkJs(abs);
|
|
184
|
+
else if (ext === '.json') problems = checkJson(abs, text);
|
|
185
|
+
else if (ext === '.jsx' || ext.startsWith('.ts') || ext === '.mts' || ext === '.cts') problems = await checkTs(abs, text, cwd);
|
|
186
|
+
else if (ext === '.css') problems = checkCss(text);
|
|
187
|
+
else if (ext === '.py') problems = await checkPy(abs);
|
|
188
|
+
return problems.filter(Boolean).slice(0, MAX_PROBLEMS);
|
|
189
|
+
} catch { return []; }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { checkFile, navigate };
|