codeply-cli 0.1.1 → 0.3.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.
Files changed (384) hide show
  1. package/THIRD_PARTY_NOTICES.md +63 -0
  2. package/bin/codeply.js +562 -259
  3. package/lib/agent.mjs +1721 -116
  4. package/lib/ai.js +1107 -184
  5. package/lib/apply-limit.js +33 -41
  6. package/lib/apply-patch.js +217 -0
  7. package/lib/arity-table.json +138 -0
  8. package/lib/arity.js +70 -0
  9. package/lib/auth.js +118 -112
  10. package/lib/commands.js +103 -0
  11. package/lib/config.js +392 -98
  12. package/lib/design-library/artifact-template.html +500 -0
  13. package/lib/design-library/assemble-artifact.mjs +21 -0
  14. package/lib/design-library/build.mjs +226 -0
  15. package/lib/design-library/cleanup.mjs +45 -0
  16. package/lib/design-library/index.json +22380 -0
  17. package/lib/design-library/make-table-data.mjs +39 -0
  18. package/lib/design-library/query.mjs +104 -0
  19. package/lib/diagnostics.js +192 -0
  20. package/lib/edit-engine.js +171 -14
  21. package/lib/github-agent.mjs +290 -0
  22. package/lib/history.js +75 -0
  23. package/lib/mcp.js +258 -0
  24. package/lib/native-tools.mjs +189 -0
  25. package/lib/oauth-connectors.js +478 -0
  26. package/lib/permissions.js +109 -0
  27. package/lib/plugins.js +353 -0
  28. package/lib/render-md.mjs +617 -617
  29. package/lib/server.mjs +457 -0
  30. package/lib/session-db.js +220 -0
  31. package/lib/share.js +168 -0
  32. package/lib/skills.js +416 -0
  33. package/lib/snapshot.js +146 -0
  34. package/lib/subagents.js +164 -0
  35. package/lib/terminal-hints.js +154 -0
  36. package/lib/textarea.mjs +258 -258
  37. package/lib/tools.mjs +1709 -73
  38. package/lib/tui.js +7 -7
  39. package/lib/tui.mjs +1086 -899
  40. package/lib/typecheck-worker.js +147 -0
  41. package/lib/web-tools.js +158 -0
  42. package/package.json +5 -3
  43. package/skills/SOURCE.md +58 -0
  44. package/skills/accessibility/SKILL.md +147 -0
  45. package/skills/agent-architecture-audit/SKILL.md +257 -0
  46. package/skills/agent-eval/SKILL.md +147 -0
  47. package/skills/agent-harness-construction/SKILL.md +74 -0
  48. package/skills/agent-introspection-debugging/SKILL.md +154 -0
  49. package/skills/agent-payment-x402/SKILL.md +225 -0
  50. package/skills/agent-self-evaluation/SKILL.md +182 -0
  51. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
  52. package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
  53. package/skills/agent-sort/SKILL.md +216 -0
  54. package/skills/agentic-engineering/SKILL.md +64 -0
  55. package/skills/agentic-os/SKILL.md +388 -0
  56. package/skills/ai-first-engineering/SKILL.md +52 -0
  57. package/skills/ai-regression-testing/SKILL.md +386 -0
  58. package/skills/android-clean-architecture/SKILL.md +340 -0
  59. package/skills/angular-developer/SKILL.md +155 -0
  60. package/skills/angular-developer/references/angular-animations.md +160 -0
  61. package/skills/angular-developer/references/angular-aria.md +410 -0
  62. package/skills/angular-developer/references/cli.md +86 -0
  63. package/skills/angular-developer/references/component-harnesses.md +59 -0
  64. package/skills/angular-developer/references/component-styling.md +91 -0
  65. package/skills/angular-developer/references/components.md +117 -0
  66. package/skills/angular-developer/references/creating-services.md +97 -0
  67. package/skills/angular-developer/references/data-resolvers.md +69 -0
  68. package/skills/angular-developer/references/define-routes.md +67 -0
  69. package/skills/angular-developer/references/defining-providers.md +72 -0
  70. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  71. package/skills/angular-developer/references/e2e-testing.md +56 -0
  72. package/skills/angular-developer/references/effects.md +83 -0
  73. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  74. package/skills/angular-developer/references/host-elements.md +80 -0
  75. package/skills/angular-developer/references/injection-context.md +63 -0
  76. package/skills/angular-developer/references/inputs.md +101 -0
  77. package/skills/angular-developer/references/linked-signal.md +59 -0
  78. package/skills/angular-developer/references/loading-strategies.md +61 -0
  79. package/skills/angular-developer/references/mcp.md +108 -0
  80. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  81. package/skills/angular-developer/references/outputs.md +86 -0
  82. package/skills/angular-developer/references/reactive-forms.md +122 -0
  83. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  84. package/skills/angular-developer/references/resource.md +77 -0
  85. package/skills/angular-developer/references/route-animations.md +56 -0
  86. package/skills/angular-developer/references/route-guards.md +52 -0
  87. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  88. package/skills/angular-developer/references/router-testing.md +87 -0
  89. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  90. package/skills/angular-developer/references/signal-forms.md +795 -0
  91. package/skills/angular-developer/references/signals-overview.md +94 -0
  92. package/skills/angular-developer/references/tailwind-css.md +69 -0
  93. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  94. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  95. package/skills/api-connector-builder/SKILL.md +121 -0
  96. package/skills/api-design/SKILL.md +524 -0
  97. package/skills/architecture-decision-records/SKILL.md +180 -0
  98. package/skills/article-writing/SKILL.md +80 -0
  99. package/skills/automation-audit-ops/SKILL.md +143 -0
  100. package/skills/autonomous-agent-harness/SKILL.md +274 -0
  101. package/skills/autonomous-loops/SKILL.md +611 -0
  102. package/skills/backend-patterns/SKILL.md +562 -0
  103. package/skills/benchmark/SKILL.md +95 -0
  104. package/skills/benchmark-methodology/SKILL.md +191 -0
  105. package/skills/benchmark-optimization-loop/SKILL.md +71 -0
  106. package/skills/blender-motion-state-inspection/SKILL.md +165 -0
  107. package/skills/blueprint/SKILL.md +106 -0
  108. package/skills/brand-discovery/SKILL.md +145 -0
  109. package/skills/brand-discovery/references/10_purpose-why.md +40 -0
  110. package/skills/brand-discovery/references/20_positioning.md +44 -0
  111. package/skills/brand-discovery/references/30_audience-niche.md +52 -0
  112. package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
  113. package/skills/brand-discovery/references/50_voice-tone.md +59 -0
  114. package/skills/brand-discovery/references/60_narrative-story.md +50 -0
  115. package/skills/brand-discovery/references/70_founder-tension.md +49 -0
  116. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
  117. package/skills/brand-voice/SKILL.md +98 -0
  118. package/skills/brand-voice/references/voice-profile-schema.md +55 -0
  119. package/skills/browser-qa/SKILL.md +105 -0
  120. package/skills/bun-runtime/SKILL.md +85 -0
  121. package/skills/canary-watch/SKILL.md +108 -0
  122. package/skills/carrier-relationship-management/SKILL.md +212 -0
  123. package/skills/cisco-ios-patterns/SKILL.md +164 -0
  124. package/skills/ck/SKILL.md +148 -0
  125. package/skills/claude-devfleet/SKILL.md +112 -0
  126. package/skills/click-path-audit/SKILL.md +245 -0
  127. package/skills/clickhouse-io/SKILL.md +445 -0
  128. package/skills/code-tour/SKILL.md +254 -0
  129. package/skills/codebase-onboarding/SKILL.md +234 -0
  130. package/skills/codehealth-mcp/SKILL.md +167 -0
  131. package/skills/coding-standards/SKILL.md +551 -0
  132. package/skills/competitive-platform-analysis/SKILL.md +214 -0
  133. package/skills/competitive-report-structure/SKILL.md +162 -0
  134. package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
  135. package/skills/config-gc/SKILL.md +120 -0
  136. package/skills/configure-ecc/SKILL.md +385 -0
  137. package/skills/connections-optimizer/SKILL.md +190 -0
  138. package/skills/content-engine/SKILL.md +132 -0
  139. package/skills/content-hash-cache-pattern/SKILL.md +162 -0
  140. package/skills/context-budget/SKILL.md +136 -0
  141. package/skills/continuous-agent-loop/SKILL.md +46 -0
  142. package/skills/continuous-learning/SKILL.md +132 -0
  143. package/skills/continuous-learning-v2/SKILL.md +361 -0
  144. package/skills/contract-first/SKILL.md +287 -0
  145. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
  146. package/skills/cost-tracking/SKILL.md +97 -0
  147. package/skills/council/SKILL.md +204 -0
  148. package/skills/cpp-coding-standards/SKILL.md +724 -0
  149. package/skills/cpp-testing/SKILL.md +325 -0
  150. package/skills/crosspost/SKILL.md +112 -0
  151. package/skills/csharp-testing/SKILL.md +322 -0
  152. package/skills/customer-billing-ops/SKILL.md +141 -0
  153. package/skills/customs-trade-compliance/SKILL.md +263 -0
  154. package/skills/dart-flutter-patterns/SKILL.md +564 -0
  155. package/skills/dashboard-builder/SKILL.md +109 -0
  156. package/skills/data-scraper-agent/SKILL.md +765 -0
  157. package/skills/data-throughput-accelerator/SKILL.md +74 -0
  158. package/skills/database-migrations/SKILL.md +430 -0
  159. package/skills/deep-research/SKILL.md +160 -0
  160. package/skills/defi-amm-security/SKILL.md +167 -0
  161. package/skills/delivery-gate/SKILL.md +126 -0
  162. package/skills/deployment-patterns/SKILL.md +428 -0
  163. package/skills/design-system/SKILL.md +83 -0
  164. package/skills/django-celery/SKILL.md +458 -0
  165. package/skills/django-patterns/SKILL.md +735 -0
  166. package/skills/django-security/SKILL.md +644 -0
  167. package/skills/django-tdd/SKILL.md +730 -0
  168. package/skills/django-verification/SKILL.md +470 -0
  169. package/skills/dmux-workflows/SKILL.md +192 -0
  170. package/skills/docker-patterns/SKILL.md +365 -0
  171. package/skills/documentation-lookup/SKILL.md +91 -0
  172. package/skills/dotnet-patterns/SKILL.md +322 -0
  173. package/skills/dynamic-workflow-mode/SKILL.md +124 -0
  174. package/skills/e2e-testing/SKILL.md +327 -0
  175. package/skills/ecc-guide/SKILL.md +190 -0
  176. package/skills/ecc-recipes/SKILL.md +149 -0
  177. package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
  178. package/skills/email-ops/SKILL.md +122 -0
  179. package/skills/energy-procurement/SKILL.md +228 -0
  180. package/skills/enterprise-agent-ops/SKILL.md +51 -0
  181. package/skills/error-handling/SKILL.md +377 -0
  182. package/skills/eval-harness/SKILL.md +271 -0
  183. package/skills/evm-token-decimals/SKILL.md +131 -0
  184. package/skills/exa-search/SKILL.md +108 -0
  185. package/skills/fal-ai-media/SKILL.md +289 -0
  186. package/skills/fastapi-patterns/SKILL.md +514 -0
  187. package/skills/finance-billing-ops/SKILL.md +128 -0
  188. package/skills/flox-environments/SKILL.md +497 -0
  189. package/skills/flutter-dart-code-review/SKILL.md +436 -0
  190. package/skills/foundation-models-on-device/SKILL.md +243 -0
  191. package/skills/frontend-a11y/SKILL.md +446 -0
  192. package/skills/frontend-design-direction/SKILL.md +93 -0
  193. package/skills/frontend-patterns/SKILL.md +657 -0
  194. package/skills/frontend-slides/SKILL.md +185 -0
  195. package/skills/fsharp-testing/SKILL.md +281 -0
  196. package/skills/gan-style-harness/SKILL.md +279 -0
  197. package/skills/gateguard/SKILL.md +133 -0
  198. package/skills/generating-python-installer/SKILL.md +820 -0
  199. package/skills/git-workflow/SKILL.md +716 -0
  200. package/skills/github-ops/SKILL.md +145 -0
  201. package/skills/golang-patterns/SKILL.md +676 -0
  202. package/skills/golang-testing/SKILL.md +721 -0
  203. package/skills/google-workspace-ops/SKILL.md +96 -0
  204. package/skills/growth-log/SKILL.md +128 -0
  205. package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
  206. package/skills/healthcare-emr-patterns/SKILL.md +160 -0
  207. package/skills/healthcare-eval-harness/SKILL.md +208 -0
  208. package/skills/healthcare-phi-compliance/SKILL.md +146 -0
  209. package/skills/hermes-imports/SKILL.md +89 -0
  210. package/skills/hexagonal-architecture/SKILL.md +277 -0
  211. package/skills/hipaa-compliance/SKILL.md +79 -0
  212. package/skills/homelab-network-readiness/SKILL.md +170 -0
  213. package/skills/homelab-network-setup/SKILL.md +130 -0
  214. package/skills/homelab-pihole-dns/SKILL.md +275 -0
  215. package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
  216. package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
  217. package/skills/hookify-rules/SKILL.md +128 -0
  218. package/skills/inherit-legacy-style/SKILL.md +157 -0
  219. package/skills/intent-driven-development/SKILL.md +360 -0
  220. package/skills/inventory-demand-planning/SKILL.md +247 -0
  221. package/skills/investor-materials/SKILL.md +97 -0
  222. package/skills/investor-outreach/SKILL.md +92 -0
  223. package/skills/ios-icon-gen/SKILL.md +158 -0
  224. package/skills/iterative-retrieval/SKILL.md +212 -0
  225. package/skills/ito-basket-compare/SKILL.md +64 -0
  226. package/skills/ito-compute/SKILL.md +129 -0
  227. package/skills/ito-data-atlas-agent/SKILL.md +64 -0
  228. package/skills/ito-market-intelligence/SKILL.md +61 -0
  229. package/skills/ito-trade-planner/SKILL.md +68 -0
  230. package/skills/java-coding-standards/SKILL.md +384 -0
  231. package/skills/jira-integration/SKILL.md +303 -0
  232. package/skills/jpa-patterns/SKILL.md +152 -0
  233. package/skills/knowledge-ops/SKILL.md +155 -0
  234. package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
  235. package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
  236. package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
  237. package/skills/kotlin-patterns/SKILL.md +712 -0
  238. package/skills/kotlin-testing/SKILL.md +825 -0
  239. package/skills/kubernetes-patterns/SKILL.md +756 -0
  240. package/skills/laravel-patterns/SKILL.md +416 -0
  241. package/skills/laravel-plugin-discovery/SKILL.md +230 -0
  242. package/skills/laravel-security/SKILL.md +948 -0
  243. package/skills/laravel-tdd/SKILL.md +675 -0
  244. package/skills/laravel-verification/SKILL.md +180 -0
  245. package/skills/latency-critical-systems/SKILL.md +75 -0
  246. package/skills/lead-intelligence/SKILL.md +322 -0
  247. package/skills/liquid-glass-design/SKILL.md +279 -0
  248. package/skills/llm-trading-agent-security/SKILL.md +147 -0
  249. package/skills/logistics-exception-management/SKILL.md +222 -0
  250. package/skills/loop-design-check/SKILL.md +143 -0
  251. package/skills/mailtrap-email-integration/SKILL.md +77 -0
  252. package/skills/make-interfaces-feel-better/SKILL.md +152 -0
  253. package/skills/manim-video/SKILL.md +90 -0
  254. package/skills/market-research/SKILL.md +76 -0
  255. package/skills/marketing-campaign/SKILL.md +114 -0
  256. package/skills/mcp-server-patterns/SKILL.md +70 -0
  257. package/skills/messages-ops/SKILL.md +105 -0
  258. package/skills/ml-adoption-playbook/SKILL.md +57 -0
  259. package/skills/mle-workflow/SKILL.md +348 -0
  260. package/skills/motion-advanced/SKILL.md +596 -0
  261. package/skills/motion-foundations/SKILL.md +299 -0
  262. package/skills/motion-patterns/SKILL.md +434 -0
  263. package/skills/motion-ui/SKILL.md +576 -0
  264. package/skills/mysql-patterns/SKILL.md +413 -0
  265. package/skills/nanoclaw-repl/SKILL.md +34 -0
  266. package/skills/nestjs-patterns/SKILL.md +231 -0
  267. package/skills/netmiko-ssh-automation/SKILL.md +174 -0
  268. package/skills/network-bgp-diagnostics/SKILL.md +168 -0
  269. package/skills/network-config-validation/SKILL.md +211 -0
  270. package/skills/network-interface-health/SKILL.md +153 -0
  271. package/skills/nextjs-turbopack/SKILL.md +58 -0
  272. package/skills/nodejs-keccak256/SKILL.md +103 -0
  273. package/skills/nutrient-document-processing/SKILL.md +168 -0
  274. package/skills/nuxt4-patterns/SKILL.md +101 -0
  275. package/skills/openclaw-persona-forge/SKILL.md +289 -0
  276. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  277. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  278. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  279. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  280. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  281. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  282. package/skills/opensource-pipeline/SKILL.md +256 -0
  283. package/skills/orch-add-feature/SKILL.md +45 -0
  284. package/skills/orch-build-mvp/SKILL.md +49 -0
  285. package/skills/orch-change-feature/SKILL.md +43 -0
  286. package/skills/orch-fix-defect/SKILL.md +43 -0
  287. package/skills/orch-pipeline/SKILL.md +121 -0
  288. package/skills/orch-refine-code/SKILL.md +44 -0
  289. package/skills/parallel-execution-optimizer/SKILL.md +74 -0
  290. package/skills/perl-patterns/SKILL.md +505 -0
  291. package/skills/perl-security/SKILL.md +504 -0
  292. package/skills/perl-testing/SKILL.md +476 -0
  293. package/skills/plan-canvas/SKILL.md +153 -0
  294. package/skills/plan-orchestrate/SKILL.md +263 -0
  295. package/skills/plankton-code-quality/SKILL.md +237 -0
  296. package/skills/postgres-patterns/SKILL.md +148 -0
  297. package/skills/prediction-market-oracle-research/SKILL.md +64 -0
  298. package/skills/prediction-market-risk-review/SKILL.md +61 -0
  299. package/skills/premium-web-design/SKILL.md +154 -0
  300. package/skills/prisma-patterns/SKILL.md +401 -0
  301. package/skills/product-capability/SKILL.md +142 -0
  302. package/skills/product-lens/SKILL.md +93 -0
  303. package/skills/production-audit/SKILL.md +207 -0
  304. package/skills/production-scheduling/SKILL.md +238 -0
  305. package/skills/project-flow-ops/SKILL.md +112 -0
  306. package/skills/prompt-optimizer/SKILL.md +398 -0
  307. package/skills/python-patterns/SKILL.md +751 -0
  308. package/skills/python-testing/SKILL.md +817 -0
  309. package/skills/pytorch-patterns/SKILL.md +397 -0
  310. package/skills/quality-nonconformance/SKILL.md +260 -0
  311. package/skills/quarkus-patterns/SKILL.md +723 -0
  312. package/skills/quarkus-security/SKILL.md +468 -0
  313. package/skills/quarkus-tdd/SKILL.md +812 -0
  314. package/skills/quarkus-verification/SKILL.md +480 -0
  315. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
  316. package/skills/react-native-patterns/SKILL.md +326 -0
  317. package/skills/react-patterns/SKILL.md +342 -0
  318. package/skills/react-performance/SKILL.md +575 -0
  319. package/skills/react-testing/SKILL.md +424 -0
  320. package/skills/recsys-pipeline-architect/SKILL.md +115 -0
  321. package/skills/recursive-decision-ledger/SKILL.md +81 -0
  322. package/skills/redis-patterns/SKILL.md +404 -0
  323. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
  324. package/skills/remotion-video-creation/SKILL.md +43 -0
  325. package/skills/repo-scan/SKILL.md +79 -0
  326. package/skills/research-ops/SKILL.md +113 -0
  327. package/skills/returns-reverse-logistics/SKILL.md +240 -0
  328. package/skills/rules-distill/SKILL.md +265 -0
  329. package/skills/rust-patterns/SKILL.md +500 -0
  330. package/skills/rust-testing/SKILL.md +501 -0
  331. package/skills/safety-guard/SKILL.md +76 -0
  332. package/skills/santa-method/SKILL.md +307 -0
  333. package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
  334. package/skills/scientific-db-uspto-database/SKILL.md +178 -0
  335. package/skills/scientific-pkg-gget/SKILL.md +167 -0
  336. package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
  337. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
  338. package/skills/search-first/SKILL.md +183 -0
  339. package/skills/security-bounty-hunter/SKILL.md +100 -0
  340. package/skills/security-review/SKILL.md +504 -0
  341. package/skills/security-scan/SKILL.md +166 -0
  342. package/skills/seo/SKILL.md +155 -0
  343. package/skills/skill-comply/SKILL.md +59 -0
  344. package/skills/skill-scout/SKILL.md +141 -0
  345. package/skills/skill-stocktake/SKILL.md +195 -0
  346. package/skills/social-graph-ranker/SKILL.md +155 -0
  347. package/skills/social-publisher/SKILL.md +130 -0
  348. package/skills/springboot-patterns/SKILL.md +315 -0
  349. package/skills/springboot-security/SKILL.md +273 -0
  350. package/skills/springboot-tdd/SKILL.md +159 -0
  351. package/skills/springboot-verification/SKILL.md +232 -0
  352. package/skills/strategic-compact/SKILL.md +142 -0
  353. package/skills/swift-actor-persistence/SKILL.md +144 -0
  354. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  355. package/skills/swift-protocol-di-testing/SKILL.md +191 -0
  356. package/skills/swiftui-patterns/SKILL.md +259 -0
  357. package/skills/taste/SKILL.md +264 -0
  358. package/skills/taste/references/genre-taxonomy.md +87 -0
  359. package/skills/tdd-workflow/SKILL.md +583 -0
  360. package/skills/team-agent-orchestration/SKILL.md +111 -0
  361. package/skills/team-builder/SKILL.md +169 -0
  362. package/skills/terminal-ops/SKILL.md +110 -0
  363. package/skills/tinystruct-patterns/SKILL.md +279 -0
  364. package/skills/tinystruct-patterns/references/architecture.md +90 -0
  365. package/skills/tinystruct-patterns/references/data-handling.md +60 -0
  366. package/skills/tinystruct-patterns/references/database.md +99 -0
  367. package/skills/tinystruct-patterns/references/routing.md +64 -0
  368. package/skills/tinystruct-patterns/references/system-usage.md +97 -0
  369. package/skills/tinystruct-patterns/references/testing.md +72 -0
  370. package/skills/token-budget-advisor/SKILL.md +134 -0
  371. package/skills/ui-demo/SKILL.md +466 -0
  372. package/skills/ui-to-vue/SKILL.md +135 -0
  373. package/skills/uncloud/SKILL.md +344 -0
  374. package/skills/unified-memory/SKILL.md +170 -0
  375. package/skills/unified-notifications-ops/SKILL.md +188 -0
  376. package/skills/verification-loop/SKILL.md +129 -0
  377. package/skills/video-editing/SKILL.md +311 -0
  378. package/skills/videodb/SKILL.md +375 -0
  379. package/skills/visa-doc-translate/SKILL.md +117 -0
  380. package/skills/vite-patterns/SKILL.md +450 -0
  381. package/skills/vue-patterns/SKILL.md +471 -0
  382. package/skills/windows-desktop-e2e/SKILL.md +888 -0
  383. package/skills/workspace-surface-audit/SKILL.md +126 -0
  384. package/skills/x-api/SKILL.md +235 -0
@@ -0,0 +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);
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Query interface for the private design reference library built in
3
+ * index.json (914 apps / 6,433 real App Store screenshots across 10
4
+ * categories, sourced from Apple's public iTunes Search API). Loaded lazily
5
+ * and cached in memory - the file is ~2MB, cheap to hold for the life of one
6
+ * Craft process, not worth re-reading per call.
7
+ */
8
+ import { readFileSync } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const INDEX_PATH = path.join(__dirname, 'index.json');
14
+
15
+ export const CATEGORY_LABELS = {
16
+ productivity: 'Productivity',
17
+ finance: 'Finance',
18
+ shopping: 'Shopping',
19
+ social: 'Social',
20
+ travel: 'Travel',
21
+ food_delivery: 'Food Delivery',
22
+ health_fitness: 'Health & Fitness',
23
+ education: 'Education',
24
+ entertainment: 'Entertainment',
25
+ real_estate: 'Real Estate',
26
+ };
27
+
28
+ let cached = null;
29
+ function load() {
30
+ if (cached) return cached;
31
+ cached = JSON.parse(readFileSync(INDEX_PATH, 'utf8'));
32
+ return cached;
33
+ }
34
+
35
+ export function designLibraryConfigured() {
36
+ try {
37
+ load();
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ export function listCategories() {
45
+ const idx = load();
46
+ return Object.entries(idx.categories).map(([key, apps]) => ({
47
+ key,
48
+ label: CATEGORY_LABELS[key] || key,
49
+ appCount: apps.length,
50
+ screenshotCount: apps.reduce((s, a) => s + a.screenshots.length, 0),
51
+ }));
52
+ }
53
+
54
+ /**
55
+ * Search the library by free-text term (matched against app name) and/or an
56
+ * exact category key. Returns apps ranked by rating count (a rough proxy for
57
+ * "this is the version of the pattern worth matching," not just whichever
58
+ * came back first), each capped to `maxScreenshotsPerApp` URLs so a broad
59
+ * query doesn't return hundreds of image links at once.
60
+ */
61
+ export function searchLibrary({ term = '', category = '', maxApps = 8, maxScreenshotsPerApp = 4 } = {}) {
62
+ const idx = load();
63
+ const normTerm = term.trim().toLowerCase();
64
+ const catKey = category.trim().toLowerCase().replace(/\s+/g, '_');
65
+
66
+ const categories = catKey && idx.categories[catKey] ? [catKey] : Object.keys(idx.categories);
67
+
68
+ function collect(matchTerm) {
69
+ const results = [];
70
+ for (const cat of categories) {
71
+ for (const app of idx.categories[cat]) {
72
+ if (matchTerm && !app.name.toLowerCase().includes(matchTerm)) continue;
73
+ results.push({ ...app, category: cat });
74
+ }
75
+ }
76
+ return results;
77
+ }
78
+
79
+ // The library only indexes app NAMES, not per-screen content - there is no
80
+ // app literally called "settings" or "onboarding", so a natural
81
+ // pattern-style query like that would always come back empty even though
82
+ // the category itself has plenty of real screens worth looking at. A given
83
+ // category is a reliable signal on its own; falling back to its top-rated
84
+ // apps (ignoring the term) beats returning nothing.
85
+ let results = collect(normTerm);
86
+ let usedFallback = false;
87
+ if (!results.length && normTerm && catKey && idx.categories[catKey]) {
88
+ results = collect('');
89
+ usedFallback = true;
90
+ }
91
+
92
+ results.sort((a, b) => (b.ratingCount || 0) - (a.ratingCount || 0));
93
+ results = results.slice(0, maxApps).map((app) => ({
94
+ name: app.name,
95
+ category: app.category,
96
+ categoryLabel: CATEGORY_LABELS[app.category] || app.category,
97
+ rating: app.rating ? Math.round(app.rating * 10) / 10 : null,
98
+ appStoreUrl: app.appStoreUrl,
99
+ screenshots: app.screenshots.slice(0, maxScreenshotsPerApp),
100
+ }));
101
+
102
+ results.usedFallback = usedFallback;
103
+ return results;
104
+ }
@@ -0,0 +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,8 +1,8 @@
1
1
  /**
2
- * Codeply CLI — surgical SEARCH/REPLACE edit engine
2
+ * Codeply CLI - surgical SEARCH/REPLACE edit engine
3
3
  *
4
4
  * Adapted from Codeply-App/main.js's computeInstructionEdits/
5
- * applySearchReplace/applyEditsToContent — same approach (small verified
5
+ * applySearchReplace/applyEditsToContent - same approach (small verified
6
6
  * diffs instead of a full-file rewrite), trimmed for a stateless single-shot
7
7
  * CLI: no local usage-history file, no .codeply/ response cache, no
8
8
  * multi-turn conversation context (each CLI invocation is its own process).
@@ -16,6 +16,15 @@ const ai = require('./ai');
16
16
  // runs), skipping blank lines on both sides. Re-indents the replacement to fit.
17
17
  // Returns { ok, content } or { ok:false, error:'notfound'|'multiple'|'empty' }.
18
18
  function applySearchReplace(fileContent, searchBlock, replaceBlock) {
19
+ // Matching works on LF text; a CRLF file is written back as CRLF so an edit
20
+ // never rewrites every line ending in the file (a whole-file diff, and a
21
+ // mixed-ending file once a later write adds LF lines).
22
+ const res = applySearchReplaceLF(fileContent, searchBlock, replaceBlock);
23
+ if (res.ok && /\r\n/.test(fileContent || '')) res.content = res.content.replace(/\r?\n/g, '\r\n');
24
+ return res;
25
+ }
26
+
27
+ function applySearchReplaceLF(fileContent, searchBlock, replaceBlock) {
19
28
  const nl = (s) => (s || '').replace(/\r\n/g, '\n');
20
29
  fileContent = nl(fileContent); searchBlock = nl(searchBlock); replaceBlock = nl(replaceBlock);
21
30
  if (!searchBlock.trim()) return { ok: false, error: 'empty' };
@@ -66,7 +75,7 @@ function applySearchReplace(fileContent, searchBlock, replaceBlock) {
66
75
  if (matches.length === 0) {
67
76
  // Last resort: anchor on the first AND last non-blank lines of the search
68
77
  // block (recovers when the AI dropped/added a line in the middle). Kept
69
- // conservative — both anchors must be unique and the span must be modest.
78
+ // conservative - both anchors must be unique and the span must be modest.
70
79
  const sNon = searchLines.map(l => l.trim()).filter(x => x !== '');
71
80
  if (sNon.length >= 2) {
72
81
  const firstHits = [], lastHits = [];
@@ -84,7 +93,17 @@ function applySearchReplace(fileContent, searchBlock, replaceBlock) {
84
93
  }
85
94
  }
86
95
  }
87
- return { ok: false, error: 'notfound' };
96
+ const fuzzy = fuzzyLocate(fileContent, searchBlock);
97
+ if (fuzzy.ok) {
98
+ const at = fileContent.indexOf(fuzzy.span);
99
+ // The model's replacement carries the indentation it wrongly assumed;
100
+ // move it to where the block really sits.
101
+ const text = fuzzy.reindent
102
+ ? reindentBlock(replaceBlock, (fuzzy.span.split('\n').find((l) => l.trim()) || '').match(/^[ \t]*/)[0])
103
+ : replaceBlock;
104
+ return { ok: true, content: fileContent.slice(0, at) + text + fileContent.slice(at + fuzzy.span.length), fuzzy: true };
105
+ }
106
+ return { ok: false, error: fuzzy.error === 'multiple' ? 'multiple' : 'notfound' };
88
107
  }
89
108
  if (matches.length > 1) return { ok: false, error: 'multiple' };
90
109
 
@@ -96,8 +115,146 @@ function applySearchReplace(fileContent, searchBlock, replaceBlock) {
96
115
  return { ok: true, content: fileContent.slice(0, startChar) + reindented + fileContent.slice(endChar) };
97
116
  }
98
117
 
118
+ // ─── Fallback matchers ──────────────────────────────────────────────────────
119
+ //
120
+ // Adapted from opencode (packages/opencode/src/tool/edit.ts), MIT License,
121
+ // Copyright (c) 2025 opencode - https://github.com/sst/opencode
122
+ //
123
+ // Each matcher yields candidate spans that exist VERBATIM in the file for a
124
+ // search block the model copied imperfectly (escaped quotes, shifted
125
+ // indentation, a middle line reworded). They only run after the matcher above
126
+ // has found nothing, and a candidate is used only when it occurs exactly once
127
+ // and is not far larger than what the model asked for.
128
+
129
+ function levenshtein(a, b) {
130
+ if (a === '' || b === '') return Math.max(a.length, b.length);
131
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
132
+ for (let i = 1; i <= a.length; i++) {
133
+ const cur = [i];
134
+ for (let j = 1; j <= b.length; j++) {
135
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
136
+ }
137
+ prev = cur;
138
+ }
139
+ return prev[b.length];
140
+ }
141
+
142
+ function spanOfLines(lines, start, end) {
143
+ return lines.slice(start, end + 1).join('\n');
144
+ }
145
+
146
+ const ANCHOR_SIMILARITY = 0.65;
147
+
148
+ // First and last lines match exactly (trimmed); the middle only has to be
149
+ // similar. Recovers a search block with one line misremembered.
150
+ function* blockAnchorMatches(content, find) {
151
+ const lines = content.split('\n');
152
+ const want = find.split('\n');
153
+ if (want[want.length - 1] === '') want.pop();
154
+ if (want.length < 3) return;
155
+ const first = want[0].trim(), last = want[want.length - 1].trim();
156
+ const maxDelta = Math.max(1, Math.floor(want.length * 0.25));
157
+
158
+ const candidates = [];
159
+ for (let i = 0; i < lines.length; i++) {
160
+ if (lines[i].trim() !== first) continue;
161
+ for (let j = i + 2; j < lines.length; j++) {
162
+ if (lines[j].trim() !== last) continue;
163
+ if (Math.abs(j - i + 1 - want.length) <= maxDelta) candidates.push([i, j]);
164
+ break;
165
+ }
166
+ }
167
+
168
+ let best = null, bestScore = -1;
169
+ for (const [s, e] of candidates) {
170
+ const size = e - s + 1;
171
+ const middle = Math.min(want.length - 2, size - 2);
172
+ let score = 1;
173
+ if (middle > 0) {
174
+ score = 0;
175
+ for (let k = 1; k < want.length - 1 && k < size - 1; k++) {
176
+ const a = lines[s + k].trim(), b = want[k].trim();
177
+ const len = Math.max(a.length, b.length);
178
+ score += len ? 1 - levenshtein(a, b) / len : 1;
179
+ }
180
+ score /= middle;
181
+ }
182
+ if (score > bestScore) { bestScore = score; best = [s, e]; }
183
+ }
184
+ if (best && bestScore >= ANCHOR_SIMILARITY) yield spanOfLines(lines, best[0], best[1]);
185
+ }
186
+
187
+ // Same block with its indentation shifted as a whole.
188
+ function* indentationFlexibleMatches(content, find) {
189
+ const dedent = (text) => {
190
+ const ls = text.split('\n');
191
+ const indents = ls.filter((l) => l.trim()).map((l) => l.match(/^\s*/)[0].length);
192
+ if (!indents.length) return text;
193
+ const min = Math.min(...indents);
194
+ return ls.map((l) => (l.trim() ? l.slice(min) : l)).join('\n');
195
+ };
196
+ const target = dedent(find);
197
+ const lines = content.split('\n');
198
+ const n = find.split('\n').length;
199
+ for (let i = 0; i + n <= lines.length; i++) {
200
+ const block = lines.slice(i, i + n).join('\n');
201
+ if (dedent(block) === target) yield block;
202
+ }
203
+ }
204
+
205
+ // The model wrote \n, \" or \` escapes where the file has the real characters.
206
+ function* escapeNormalizedMatches(content, find) {
207
+ const unescape = (s) => s.replace(/\\(n|t|r|'|"|`|\\|\n|\$)/g, (m, c) =>
208
+ ({ n: '\n', t: '\t', r: '\r', "'": "'", '"': '"', '`': '`', '\\': '\\', '\n': '\n', $: '$' }[c] ?? m));
209
+ const target = unescape(find);
210
+ if (target !== find && content.includes(target)) yield target;
211
+ const lines = content.split('\n');
212
+ const n = target.split('\n').length;
213
+ for (let i = 0; i + n <= lines.length; i++) {
214
+ const block = lines.slice(i, i + n).join('\n');
215
+ if (block !== target && unescape(block) === target) yield block;
216
+ }
217
+ }
218
+
219
+ // Stray blank lines or spaces around the block.
220
+ function* trimmedBoundaryMatches(content, find) {
221
+ const t = find.trim();
222
+ if (t === find) return;
223
+ if (content.includes(t)) yield t;
224
+ }
225
+
226
+ const FALLBACK_MATCHERS = [indentationFlexibleMatches, escapeNormalizedMatches, trimmedBoundaryMatches, blockAnchorMatches];
227
+
228
+ // A "match" many times bigger than the search block means an anchor latched
229
+ // onto the wrong closing line; replacing it would silently delete code.
230
+ function isDisproportionate(span, find) {
231
+ const spanLines = span.split('\n').length, findLines = find.split('\n').length;
232
+ if (spanLines >= Math.max(findLines + 3, findLines * 2)) return true;
233
+ if (findLines === 1) return false;
234
+ return span.trim().length > Math.max(find.trim().length + 500, find.trim().length * 4);
235
+ }
236
+
237
+ /**
238
+ * Find the one verbatim span in `content` that an imperfect search block most
239
+ * likely meant. @returns {{ok:true, span:string} | {ok:false, error:'notfound'|'multiple'|'disproportionate'}}
240
+ */
241
+ function fuzzyLocate(content, find) {
242
+ let sawMultiple = false;
243
+ for (const matcher of FALLBACK_MATCHERS) {
244
+ for (const span of matcher(content, find)) {
245
+ if (!span.trim()) continue;
246
+ const at = content.indexOf(span);
247
+ if (at === -1) continue;
248
+ if (at !== content.lastIndexOf(span)) { sawMultiple = true; continue; }
249
+ if (isDisproportionate(span, find)) return { ok: false, error: 'disproportionate' };
250
+ return { ok: true, span, reindent: matcher === indentationFlexibleMatches || matcher === blockAnchorMatches };
251
+ }
252
+ }
253
+ return { ok: false, error: sawMultiple ? 'multiple' : 'notfound' };
254
+ }
255
+
99
256
  // Apply an ordered list of verified {search, replace} hunks to file content,
100
- // all-or-nothing (pure — no disk write).
257
+ // all-or-nothing (pure - no disk write).
101
258
  function applyEditsToContent(fileContent, edits) {
102
259
  let working = (fileContent || '').replace(/\r\n/g, '\n');
103
260
  for (let i = 0; i < edits.length; i++) {
@@ -109,14 +266,14 @@ function applyEditsToContent(fileContent, edits) {
109
266
  return { ok: true, content: working };
110
267
  }
111
268
 
112
- const SYSTEM_PROMPT = `You are a precise code editor. You receive an INSTRUCTION describing a small change, and the FULL current file. Make ONLY the specific change requested, as surgical SEARCH/REPLACE edits — do NOT reproduce the whole file.
269
+ const SYSTEM_PROMPT = `You are a precise code editor. You receive an INSTRUCTION describing a small change, and the FULL current file. Make ONLY the specific change requested, as surgical SEARCH/REPLACE edits - do NOT reproduce the whole file.
113
270
 
114
271
  STRICT RULES:
115
272
  1. Return an "edits" array. Use the FEWEST edits that cleanly express the change.
116
- 2. "search" must be copied VERBATIM from the file below — exact existing text being changed, with just enough surrounding lines to be unique.
117
- 3. "replace" is that same block with ONLY the requested change applied — preserve everything else in it exactly (formatting, unrelated properties, comments, whitespace style).
118
- 4. If the SAME property/selector/value appears more than once in the file (e.g. "top" set in both a "from" and a "to" block), do NOT pick the first or most obvious match — use every clue in the instruction to find the ONE correct occurrence: the CURRENT value it mentions, nearby selectors, rule/keyframe names, or ordering (first/last, start/end). If more than one occurrence still fits equally well after that, return an EMPTY "edits" array with a "reason" naming the ambiguity instead of guessing.
119
- 5. If the instruction genuinely requires large new content that can't be expressed as a small edit (e.g. "build a whole new page/game/module from scratch"), return an EMPTY "edits" array — do not guess badly at a huge diff.
273
+ 2. "search" must be copied VERBATIM from the file below - exact existing text being changed, with just enough surrounding lines to be unique.
274
+ 3. "replace" is that same block with ONLY the requested change applied - preserve everything else in it exactly (formatting, unrelated properties, comments, whitespace style).
275
+ 4. If the SAME property/selector/value appears more than once in the file (e.g. "top" set in both a "from" and a "to" block), do NOT pick the first or most obvious match - use every clue in the instruction to find the ONE correct occurrence: the CURRENT value it mentions, nearby selectors, rule/keyframe names, or ordering (first/last, start/end). If more than one occurrence still fits equally well after that, return an EMPTY "edits" array with a "reason" naming the ambiguity instead of guessing.
276
+ 5. If the instruction genuinely requires large new content that can't be expressed as a small edit (e.g. "build a whole new page/game/module from scratch"), return an EMPTY "edits" array - do not guess badly at a huge diff.
120
277
  6. Return ONLY valid JSON, no markdown, no commentary.
121
278
 
122
279
  Response format:
@@ -148,7 +305,7 @@ async function computeInstructionEdits(instruction, filePath) {
148
305
  const messages = [
149
306
  { role: 'system', content: SYSTEM_PROMPT },
150
307
  // Large/stable content (the file) first, small/always-different content
151
- // (the instruction) last — the ai-proxy's underlying model caches a
308
+ // (the instruction) last - the ai-proxy's underlying model caches a
152
309
  // repeated PREFIX, so this ordering matters for repeated calls against
153
310
  // the same file even when the instruction text differs each time.
154
311
  { role: 'user', content: `FULL FILE (${path.basename(filePath)}):\n${content}\n\nINSTRUCTION:\n${instruction}` },
@@ -188,8 +345,8 @@ async function computeInstructionEdits(instruction, filePath) {
188
345
  const feedback =
189
346
  `Some "search" blocks from your last answer are wrong. Return the COMPLETE corrected edits array again, fixing these:\n` +
190
347
  bad.map(c => c.status === 'multiple'
191
- ? `- Matched MULTIPLE places — include more surrounding lines so it is unique:\n${c.e.search}`
192
- : `- NOT found in the file — copy it EXACTLY from the FULL FILE above, character for character:\n${c.e.search}`
348
+ ? `- Matched MULTIPLE places - include more surrounding lines so it is unique:\n${c.e.search}`
349
+ : `- NOT found in the file - copy it EXACTLY from the FULL FILE above, character for character:\n${c.e.search}`
193
350
  ).join('\n') +
194
351
  `\nEvery "search" must be copied verbatim from the file shown above.`;
195
352
  let retry;
@@ -204,4 +361,4 @@ async function computeInstructionEdits(instruction, filePath) {
204
361
  return { success: true, edits: good, badCount: bad.length, confidence, reason, tokensUsed: totalTokens, modelUsed: lastModelUsed };
205
362
  }
206
363
 
207
- module.exports = { applySearchReplace, applyEditsToContent, computeInstructionEdits };
364
+ module.exports = { applySearchReplace, applyEditsToContent, computeInstructionEdits, fuzzyLocate };