codeply-cli 0.1.0 → 0.2.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 (351) hide show
  1. package/bin/codeply.js +125 -14
  2. package/lib/agent.mjs +157 -18
  3. package/lib/ai.js +221 -15
  4. package/lib/apply-limit.js +13 -3
  5. package/lib/config.js +63 -16
  6. package/lib/edit-engine.js +1 -1
  7. package/lib/skills.js +358 -0
  8. package/lib/tools.mjs +63 -6
  9. package/lib/tui.mjs +96 -3
  10. package/package.json +3 -2
  11. package/skills/SOURCE.md +58 -0
  12. package/skills/accessibility/SKILL.md +147 -0
  13. package/skills/agent-architecture-audit/SKILL.md +257 -0
  14. package/skills/agent-eval/SKILL.md +147 -0
  15. package/skills/agent-harness-construction/SKILL.md +74 -0
  16. package/skills/agent-introspection-debugging/SKILL.md +154 -0
  17. package/skills/agent-payment-x402/SKILL.md +225 -0
  18. package/skills/agent-self-evaluation/SKILL.md +182 -0
  19. package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
  20. package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
  21. package/skills/agent-sort/SKILL.md +216 -0
  22. package/skills/agentic-engineering/SKILL.md +64 -0
  23. package/skills/agentic-os/SKILL.md +388 -0
  24. package/skills/ai-first-engineering/SKILL.md +52 -0
  25. package/skills/ai-regression-testing/SKILL.md +386 -0
  26. package/skills/android-clean-architecture/SKILL.md +340 -0
  27. package/skills/angular-developer/SKILL.md +155 -0
  28. package/skills/angular-developer/references/angular-animations.md +160 -0
  29. package/skills/angular-developer/references/angular-aria.md +410 -0
  30. package/skills/angular-developer/references/cli.md +86 -0
  31. package/skills/angular-developer/references/component-harnesses.md +59 -0
  32. package/skills/angular-developer/references/component-styling.md +91 -0
  33. package/skills/angular-developer/references/components.md +117 -0
  34. package/skills/angular-developer/references/creating-services.md +97 -0
  35. package/skills/angular-developer/references/data-resolvers.md +69 -0
  36. package/skills/angular-developer/references/define-routes.md +67 -0
  37. package/skills/angular-developer/references/defining-providers.md +72 -0
  38. package/skills/angular-developer/references/di-fundamentals.md +120 -0
  39. package/skills/angular-developer/references/e2e-testing.md +56 -0
  40. package/skills/angular-developer/references/effects.md +83 -0
  41. package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  42. package/skills/angular-developer/references/host-elements.md +80 -0
  43. package/skills/angular-developer/references/injection-context.md +63 -0
  44. package/skills/angular-developer/references/inputs.md +101 -0
  45. package/skills/angular-developer/references/linked-signal.md +59 -0
  46. package/skills/angular-developer/references/loading-strategies.md +61 -0
  47. package/skills/angular-developer/references/mcp.md +108 -0
  48. package/skills/angular-developer/references/navigate-to-routes.md +69 -0
  49. package/skills/angular-developer/references/outputs.md +86 -0
  50. package/skills/angular-developer/references/reactive-forms.md +122 -0
  51. package/skills/angular-developer/references/rendering-strategies.md +44 -0
  52. package/skills/angular-developer/references/resource.md +77 -0
  53. package/skills/angular-developer/references/route-animations.md +56 -0
  54. package/skills/angular-developer/references/route-guards.md +52 -0
  55. package/skills/angular-developer/references/router-lifecycle.md +45 -0
  56. package/skills/angular-developer/references/router-testing.md +87 -0
  57. package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  58. package/skills/angular-developer/references/signal-forms.md +795 -0
  59. package/skills/angular-developer/references/signals-overview.md +94 -0
  60. package/skills/angular-developer/references/tailwind-css.md +69 -0
  61. package/skills/angular-developer/references/template-driven-forms.md +114 -0
  62. package/skills/angular-developer/references/testing-fundamentals.md +65 -0
  63. package/skills/api-connector-builder/SKILL.md +121 -0
  64. package/skills/api-design/SKILL.md +524 -0
  65. package/skills/architecture-decision-records/SKILL.md +180 -0
  66. package/skills/article-writing/SKILL.md +80 -0
  67. package/skills/automation-audit-ops/SKILL.md +143 -0
  68. package/skills/autonomous-agent-harness/SKILL.md +274 -0
  69. package/skills/autonomous-loops/SKILL.md +611 -0
  70. package/skills/backend-patterns/SKILL.md +562 -0
  71. package/skills/benchmark/SKILL.md +95 -0
  72. package/skills/benchmark-methodology/SKILL.md +191 -0
  73. package/skills/benchmark-optimization-loop/SKILL.md +71 -0
  74. package/skills/blender-motion-state-inspection/SKILL.md +165 -0
  75. package/skills/blueprint/SKILL.md +106 -0
  76. package/skills/brand-discovery/SKILL.md +145 -0
  77. package/skills/brand-discovery/references/10_purpose-why.md +40 -0
  78. package/skills/brand-discovery/references/20_positioning.md +44 -0
  79. package/skills/brand-discovery/references/30_audience-niche.md +52 -0
  80. package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
  81. package/skills/brand-discovery/references/50_voice-tone.md +59 -0
  82. package/skills/brand-discovery/references/60_narrative-story.md +50 -0
  83. package/skills/brand-discovery/references/70_founder-tension.md +49 -0
  84. package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
  85. package/skills/brand-voice/SKILL.md +98 -0
  86. package/skills/brand-voice/references/voice-profile-schema.md +55 -0
  87. package/skills/browser-qa/SKILL.md +105 -0
  88. package/skills/bun-runtime/SKILL.md +85 -0
  89. package/skills/canary-watch/SKILL.md +108 -0
  90. package/skills/carrier-relationship-management/SKILL.md +212 -0
  91. package/skills/cisco-ios-patterns/SKILL.md +164 -0
  92. package/skills/ck/SKILL.md +148 -0
  93. package/skills/claude-devfleet/SKILL.md +112 -0
  94. package/skills/click-path-audit/SKILL.md +245 -0
  95. package/skills/clickhouse-io/SKILL.md +445 -0
  96. package/skills/code-tour/SKILL.md +254 -0
  97. package/skills/codebase-onboarding/SKILL.md +234 -0
  98. package/skills/codehealth-mcp/SKILL.md +167 -0
  99. package/skills/coding-standards/SKILL.md +551 -0
  100. package/skills/competitive-platform-analysis/SKILL.md +214 -0
  101. package/skills/competitive-report-structure/SKILL.md +162 -0
  102. package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
  103. package/skills/config-gc/SKILL.md +120 -0
  104. package/skills/configure-ecc/SKILL.md +385 -0
  105. package/skills/connections-optimizer/SKILL.md +190 -0
  106. package/skills/content-engine/SKILL.md +132 -0
  107. package/skills/content-hash-cache-pattern/SKILL.md +162 -0
  108. package/skills/context-budget/SKILL.md +136 -0
  109. package/skills/continuous-agent-loop/SKILL.md +46 -0
  110. package/skills/continuous-learning/SKILL.md +132 -0
  111. package/skills/continuous-learning-v2/SKILL.md +361 -0
  112. package/skills/contract-first/SKILL.md +287 -0
  113. package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
  114. package/skills/cost-tracking/SKILL.md +97 -0
  115. package/skills/council/SKILL.md +204 -0
  116. package/skills/cpp-coding-standards/SKILL.md +724 -0
  117. package/skills/cpp-testing/SKILL.md +325 -0
  118. package/skills/crosspost/SKILL.md +112 -0
  119. package/skills/csharp-testing/SKILL.md +322 -0
  120. package/skills/customer-billing-ops/SKILL.md +141 -0
  121. package/skills/customs-trade-compliance/SKILL.md +263 -0
  122. package/skills/dart-flutter-patterns/SKILL.md +564 -0
  123. package/skills/dashboard-builder/SKILL.md +109 -0
  124. package/skills/data-scraper-agent/SKILL.md +765 -0
  125. package/skills/data-throughput-accelerator/SKILL.md +74 -0
  126. package/skills/database-migrations/SKILL.md +430 -0
  127. package/skills/deep-research/SKILL.md +160 -0
  128. package/skills/defi-amm-security/SKILL.md +167 -0
  129. package/skills/delivery-gate/SKILL.md +126 -0
  130. package/skills/deployment-patterns/SKILL.md +428 -0
  131. package/skills/design-system/SKILL.md +83 -0
  132. package/skills/django-celery/SKILL.md +458 -0
  133. package/skills/django-patterns/SKILL.md +735 -0
  134. package/skills/django-security/SKILL.md +644 -0
  135. package/skills/django-tdd/SKILL.md +730 -0
  136. package/skills/django-verification/SKILL.md +470 -0
  137. package/skills/dmux-workflows/SKILL.md +192 -0
  138. package/skills/docker-patterns/SKILL.md +365 -0
  139. package/skills/documentation-lookup/SKILL.md +91 -0
  140. package/skills/dotnet-patterns/SKILL.md +322 -0
  141. package/skills/dynamic-workflow-mode/SKILL.md +124 -0
  142. package/skills/e2e-testing/SKILL.md +327 -0
  143. package/skills/ecc-guide/SKILL.md +190 -0
  144. package/skills/ecc-recipes/SKILL.md +149 -0
  145. package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
  146. package/skills/email-ops/SKILL.md +122 -0
  147. package/skills/energy-procurement/SKILL.md +228 -0
  148. package/skills/enterprise-agent-ops/SKILL.md +51 -0
  149. package/skills/error-handling/SKILL.md +377 -0
  150. package/skills/eval-harness/SKILL.md +271 -0
  151. package/skills/evm-token-decimals/SKILL.md +131 -0
  152. package/skills/exa-search/SKILL.md +108 -0
  153. package/skills/fal-ai-media/SKILL.md +289 -0
  154. package/skills/fastapi-patterns/SKILL.md +514 -0
  155. package/skills/finance-billing-ops/SKILL.md +128 -0
  156. package/skills/flox-environments/SKILL.md +497 -0
  157. package/skills/flutter-dart-code-review/SKILL.md +436 -0
  158. package/skills/foundation-models-on-device/SKILL.md +243 -0
  159. package/skills/frontend-a11y/SKILL.md +446 -0
  160. package/skills/frontend-design-direction/SKILL.md +93 -0
  161. package/skills/frontend-patterns/SKILL.md +657 -0
  162. package/skills/frontend-slides/SKILL.md +185 -0
  163. package/skills/fsharp-testing/SKILL.md +281 -0
  164. package/skills/gan-style-harness/SKILL.md +279 -0
  165. package/skills/gateguard/SKILL.md +133 -0
  166. package/skills/generating-python-installer/SKILL.md +820 -0
  167. package/skills/git-workflow/SKILL.md +716 -0
  168. package/skills/github-ops/SKILL.md +145 -0
  169. package/skills/golang-patterns/SKILL.md +676 -0
  170. package/skills/golang-testing/SKILL.md +721 -0
  171. package/skills/google-workspace-ops/SKILL.md +96 -0
  172. package/skills/growth-log/SKILL.md +128 -0
  173. package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
  174. package/skills/healthcare-emr-patterns/SKILL.md +160 -0
  175. package/skills/healthcare-eval-harness/SKILL.md +208 -0
  176. package/skills/healthcare-phi-compliance/SKILL.md +146 -0
  177. package/skills/hermes-imports/SKILL.md +89 -0
  178. package/skills/hexagonal-architecture/SKILL.md +277 -0
  179. package/skills/hipaa-compliance/SKILL.md +79 -0
  180. package/skills/homelab-network-readiness/SKILL.md +170 -0
  181. package/skills/homelab-network-setup/SKILL.md +130 -0
  182. package/skills/homelab-pihole-dns/SKILL.md +275 -0
  183. package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
  184. package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
  185. package/skills/hookify-rules/SKILL.md +128 -0
  186. package/skills/inherit-legacy-style/SKILL.md +157 -0
  187. package/skills/intent-driven-development/SKILL.md +360 -0
  188. package/skills/inventory-demand-planning/SKILL.md +247 -0
  189. package/skills/investor-materials/SKILL.md +97 -0
  190. package/skills/investor-outreach/SKILL.md +92 -0
  191. package/skills/ios-icon-gen/SKILL.md +158 -0
  192. package/skills/iterative-retrieval/SKILL.md +212 -0
  193. package/skills/ito-basket-compare/SKILL.md +64 -0
  194. package/skills/ito-compute/SKILL.md +129 -0
  195. package/skills/ito-data-atlas-agent/SKILL.md +64 -0
  196. package/skills/ito-market-intelligence/SKILL.md +61 -0
  197. package/skills/ito-trade-planner/SKILL.md +68 -0
  198. package/skills/java-coding-standards/SKILL.md +384 -0
  199. package/skills/jira-integration/SKILL.md +303 -0
  200. package/skills/jpa-patterns/SKILL.md +152 -0
  201. package/skills/knowledge-ops/SKILL.md +155 -0
  202. package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
  203. package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
  204. package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
  205. package/skills/kotlin-patterns/SKILL.md +712 -0
  206. package/skills/kotlin-testing/SKILL.md +825 -0
  207. package/skills/kubernetes-patterns/SKILL.md +756 -0
  208. package/skills/laravel-patterns/SKILL.md +416 -0
  209. package/skills/laravel-plugin-discovery/SKILL.md +230 -0
  210. package/skills/laravel-security/SKILL.md +948 -0
  211. package/skills/laravel-tdd/SKILL.md +675 -0
  212. package/skills/laravel-verification/SKILL.md +180 -0
  213. package/skills/latency-critical-systems/SKILL.md +75 -0
  214. package/skills/lead-intelligence/SKILL.md +322 -0
  215. package/skills/liquid-glass-design/SKILL.md +279 -0
  216. package/skills/llm-trading-agent-security/SKILL.md +147 -0
  217. package/skills/logistics-exception-management/SKILL.md +222 -0
  218. package/skills/loop-design-check/SKILL.md +143 -0
  219. package/skills/mailtrap-email-integration/SKILL.md +77 -0
  220. package/skills/make-interfaces-feel-better/SKILL.md +152 -0
  221. package/skills/manim-video/SKILL.md +90 -0
  222. package/skills/market-research/SKILL.md +76 -0
  223. package/skills/marketing-campaign/SKILL.md +114 -0
  224. package/skills/mcp-server-patterns/SKILL.md +70 -0
  225. package/skills/messages-ops/SKILL.md +105 -0
  226. package/skills/ml-adoption-playbook/SKILL.md +57 -0
  227. package/skills/mle-workflow/SKILL.md +348 -0
  228. package/skills/motion-advanced/SKILL.md +596 -0
  229. package/skills/motion-foundations/SKILL.md +299 -0
  230. package/skills/motion-patterns/SKILL.md +434 -0
  231. package/skills/motion-ui/SKILL.md +576 -0
  232. package/skills/mysql-patterns/SKILL.md +413 -0
  233. package/skills/nanoclaw-repl/SKILL.md +34 -0
  234. package/skills/nestjs-patterns/SKILL.md +231 -0
  235. package/skills/netmiko-ssh-automation/SKILL.md +174 -0
  236. package/skills/network-bgp-diagnostics/SKILL.md +168 -0
  237. package/skills/network-config-validation/SKILL.md +211 -0
  238. package/skills/network-interface-health/SKILL.md +153 -0
  239. package/skills/nextjs-turbopack/SKILL.md +58 -0
  240. package/skills/nodejs-keccak256/SKILL.md +103 -0
  241. package/skills/nutrient-document-processing/SKILL.md +168 -0
  242. package/skills/nuxt4-patterns/SKILL.md +101 -0
  243. package/skills/openclaw-persona-forge/SKILL.md +289 -0
  244. package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
  245. package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
  246. package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
  247. package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
  248. package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
  249. package/skills/openclaw-persona-forge/references/output-template.md +166 -0
  250. package/skills/opensource-pipeline/SKILL.md +256 -0
  251. package/skills/orch-add-feature/SKILL.md +45 -0
  252. package/skills/orch-build-mvp/SKILL.md +49 -0
  253. package/skills/orch-change-feature/SKILL.md +43 -0
  254. package/skills/orch-fix-defect/SKILL.md +43 -0
  255. package/skills/orch-pipeline/SKILL.md +121 -0
  256. package/skills/orch-refine-code/SKILL.md +44 -0
  257. package/skills/parallel-execution-optimizer/SKILL.md +74 -0
  258. package/skills/perl-patterns/SKILL.md +505 -0
  259. package/skills/perl-security/SKILL.md +504 -0
  260. package/skills/perl-testing/SKILL.md +476 -0
  261. package/skills/plan-canvas/SKILL.md +153 -0
  262. package/skills/plan-orchestrate/SKILL.md +263 -0
  263. package/skills/plankton-code-quality/SKILL.md +237 -0
  264. package/skills/postgres-patterns/SKILL.md +148 -0
  265. package/skills/prediction-market-oracle-research/SKILL.md +64 -0
  266. package/skills/prediction-market-risk-review/SKILL.md +61 -0
  267. package/skills/prisma-patterns/SKILL.md +401 -0
  268. package/skills/product-capability/SKILL.md +142 -0
  269. package/skills/product-lens/SKILL.md +93 -0
  270. package/skills/production-audit/SKILL.md +207 -0
  271. package/skills/production-scheduling/SKILL.md +238 -0
  272. package/skills/project-flow-ops/SKILL.md +112 -0
  273. package/skills/prompt-optimizer/SKILL.md +398 -0
  274. package/skills/python-patterns/SKILL.md +751 -0
  275. package/skills/python-testing/SKILL.md +817 -0
  276. package/skills/pytorch-patterns/SKILL.md +397 -0
  277. package/skills/quality-nonconformance/SKILL.md +260 -0
  278. package/skills/quarkus-patterns/SKILL.md +723 -0
  279. package/skills/quarkus-security/SKILL.md +468 -0
  280. package/skills/quarkus-tdd/SKILL.md +812 -0
  281. package/skills/quarkus-verification/SKILL.md +480 -0
  282. package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
  283. package/skills/react-native-patterns/SKILL.md +326 -0
  284. package/skills/react-patterns/SKILL.md +342 -0
  285. package/skills/react-performance/SKILL.md +575 -0
  286. package/skills/react-testing/SKILL.md +424 -0
  287. package/skills/recsys-pipeline-architect/SKILL.md +115 -0
  288. package/skills/recursive-decision-ledger/SKILL.md +81 -0
  289. package/skills/redis-patterns/SKILL.md +404 -0
  290. package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
  291. package/skills/remotion-video-creation/SKILL.md +43 -0
  292. package/skills/repo-scan/SKILL.md +79 -0
  293. package/skills/research-ops/SKILL.md +113 -0
  294. package/skills/returns-reverse-logistics/SKILL.md +240 -0
  295. package/skills/rules-distill/SKILL.md +265 -0
  296. package/skills/rust-patterns/SKILL.md +500 -0
  297. package/skills/rust-testing/SKILL.md +501 -0
  298. package/skills/safety-guard/SKILL.md +76 -0
  299. package/skills/santa-method/SKILL.md +307 -0
  300. package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
  301. package/skills/scientific-db-uspto-database/SKILL.md +178 -0
  302. package/skills/scientific-pkg-gget/SKILL.md +167 -0
  303. package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
  304. package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
  305. package/skills/search-first/SKILL.md +183 -0
  306. package/skills/security-bounty-hunter/SKILL.md +100 -0
  307. package/skills/security-review/SKILL.md +504 -0
  308. package/skills/security-scan/SKILL.md +166 -0
  309. package/skills/seo/SKILL.md +155 -0
  310. package/skills/skill-comply/SKILL.md +59 -0
  311. package/skills/skill-scout/SKILL.md +141 -0
  312. package/skills/skill-stocktake/SKILL.md +195 -0
  313. package/skills/social-graph-ranker/SKILL.md +155 -0
  314. package/skills/social-publisher/SKILL.md +130 -0
  315. package/skills/springboot-patterns/SKILL.md +315 -0
  316. package/skills/springboot-security/SKILL.md +273 -0
  317. package/skills/springboot-tdd/SKILL.md +159 -0
  318. package/skills/springboot-verification/SKILL.md +232 -0
  319. package/skills/strategic-compact/SKILL.md +142 -0
  320. package/skills/swift-actor-persistence/SKILL.md +144 -0
  321. package/skills/swift-concurrency-6-2/SKILL.md +216 -0
  322. package/skills/swift-protocol-di-testing/SKILL.md +191 -0
  323. package/skills/swiftui-patterns/SKILL.md +259 -0
  324. package/skills/taste/SKILL.md +264 -0
  325. package/skills/taste/references/genre-taxonomy.md +87 -0
  326. package/skills/tdd-workflow/SKILL.md +583 -0
  327. package/skills/team-agent-orchestration/SKILL.md +111 -0
  328. package/skills/team-builder/SKILL.md +169 -0
  329. package/skills/terminal-ops/SKILL.md +110 -0
  330. package/skills/tinystruct-patterns/SKILL.md +279 -0
  331. package/skills/tinystruct-patterns/references/architecture.md +90 -0
  332. package/skills/tinystruct-patterns/references/data-handling.md +60 -0
  333. package/skills/tinystruct-patterns/references/database.md +99 -0
  334. package/skills/tinystruct-patterns/references/routing.md +64 -0
  335. package/skills/tinystruct-patterns/references/system-usage.md +97 -0
  336. package/skills/tinystruct-patterns/references/testing.md +72 -0
  337. package/skills/token-budget-advisor/SKILL.md +134 -0
  338. package/skills/ui-demo/SKILL.md +466 -0
  339. package/skills/ui-to-vue/SKILL.md +135 -0
  340. package/skills/uncloud/SKILL.md +344 -0
  341. package/skills/unified-memory/SKILL.md +170 -0
  342. package/skills/unified-notifications-ops/SKILL.md +188 -0
  343. package/skills/verification-loop/SKILL.md +129 -0
  344. package/skills/video-editing/SKILL.md +311 -0
  345. package/skills/videodb/SKILL.md +375 -0
  346. package/skills/visa-doc-translate/SKILL.md +117 -0
  347. package/skills/vite-patterns/SKILL.md +450 -0
  348. package/skills/vue-patterns/SKILL.md +471 -0
  349. package/skills/windows-desktop-e2e/SKILL.md +888 -0
  350. package/skills/workspace-surface-audit/SKILL.md +126 -0
  351. package/skills/x-api/SKILL.md +235 -0
package/bin/codeply.js CHANGED
@@ -73,6 +73,7 @@ async function runApply(instruction, opts) {
73
73
  const limit = await applyLimit.checkApplyLimit();
74
74
  if (!limit.allowed) {
75
75
  console.error(limit.error || `Daily apply limit reached (${limit.count}/${limit.limit}). This is shared with the Codeply desktop app and resets at midnight UTC.`);
76
+ console.error(c.muted(config.byokHint()));
76
77
  process.exitCode = 1;
77
78
  return;
78
79
  }
@@ -116,7 +117,11 @@ async function runApply(instruction, opts) {
116
117
  }
117
118
 
118
119
  fs.writeFileSync(filePath, applied.content);
119
- if (usingCodeply) await applyLimit.recordApplyEvent(filePath);
120
+ if (usingCodeply) {
121
+ const linesAdded = result.edits.reduce((n, e) => n + e.replace.split('\n').length, 0);
122
+ const linesRemoved = result.edits.reduce((n, e) => n + e.search.split('\n').length, 0);
123
+ await applyLimit.recordApplyEvent(filePath, linesAdded, linesRemoved);
124
+ }
120
125
  console.log(c.green(`✓ Applied to ${filePath}`));
121
126
  }
122
127
 
@@ -198,10 +203,10 @@ program
198
203
 
199
204
  program
200
205
  .command('provider [name]')
201
- .description('Show or set the model backend (codeply | ollama).')
202
- .option('--key <key>', 'Ollama API key (cloud only; omit for a local daemon)')
203
- .option('--model <model>', 'Ollama model, e.g. gemma4:31b')
204
- .option('--host <url>', 'Ollama host (default https://ollama.com; local: http://localhost:11434)')
206
+ .description('Show or set the model backend (codeply | ollama | openrouter | groq | anthropic | openai).')
207
+ .option('--key <key>', 'API key for ollama (cloud only), openrouter, groq, anthropic, or openai')
208
+ .option('--model <model>', 'model name for the selected provider')
209
+ .option('--host <url>', 'ollama host only (default https://ollama.com; local: http://localhost:11434)')
205
210
  .action(async (name, opts) => {
206
211
  const config = require('../lib/config');
207
212
 
@@ -212,25 +217,41 @@ program
212
217
  console.log(`Host ${cfg.ollama.host}`);
213
218
  console.log(`Model ${cfg.ollama.model}`);
214
219
  console.log(`Key ${config.maskKey(cfg.ollama.apiKey)}`);
220
+ } else if (config.BYOK_PROVIDERS.includes(cfg.provider)) {
221
+ console.log(`Model ${cfg[cfg.provider].model}`);
222
+ console.log(`Key ${config.maskKey(cfg[cfg.provider].apiKey)}`);
223
+ }
224
+ console.log(c.muted(`\nConfig ${config.configPath}`));
225
+ console.log(c.muted('Providers codeply, ollama, openrouter, groq, anthropic, openai'));
226
+ console.log(c.muted('Set with codeply provider openrouter --key <key> [--model <model>]'));
227
+ if (cfg.provider === 'codeply') {
228
+ console.log(c.muted(`\n${config.byokHint()}`));
215
229
  }
216
- console.log(c.muted(`\nConfig ${config.configPath}`));
217
- console.log(c.muted('Set with codeply provider ollama --key <key> --model gemma4:31b'));
218
230
  return;
219
231
  }
220
232
 
221
- if (name && name !== 'codeply' && name !== 'ollama') {
222
- console.error(`Unknown provider "${name}". Use "codeply" or "ollama".`);
233
+ if (name && !config.PROVIDERS.includes(name)) {
234
+ console.error(`Unknown provider "${name}". Use one of: ${config.PROVIDERS.join(', ')}.`);
223
235
  process.exitCode = 1;
224
236
  return;
225
237
  }
226
238
 
227
239
  const patch = {};
228
240
  if (name) patch.provider = name;
229
- const ollama = {};
230
- if (opts.key) ollama.apiKey = opts.key;
231
- if (opts.model) ollama.model = opts.model;
232
- if (opts.host) ollama.host = opts.host;
233
- if (Object.keys(ollama).length) patch.ollama = ollama;
241
+ const target = name || config.getConfig().provider;
242
+
243
+ if (target === 'ollama') {
244
+ const ollama = {};
245
+ if (opts.key) ollama.apiKey = opts.key;
246
+ if (opts.model) ollama.model = opts.model;
247
+ if (opts.host) ollama.host = opts.host;
248
+ if (Object.keys(ollama).length) patch.ollama = ollama;
249
+ } else if (config.BYOK_PROVIDERS.includes(target)) {
250
+ const byok = {};
251
+ if (opts.key) byok.apiKey = opts.key;
252
+ if (opts.model) byok.model = opts.model;
253
+ if (Object.keys(byok).length) patch[target] = byok;
254
+ }
234
255
 
235
256
  const saved = config.saveConfig(patch);
236
257
  if (!saved.ok) {
@@ -251,6 +272,96 @@ program
251
272
  .option('--dry-run', 'show the proposed edits without writing them')
252
273
  .action(runApply);
253
274
 
275
+ const skillCmd = program
276
+ .command('skill')
277
+ .description('List, install, or remove agent skills (SKILL.md playbooks the agent can load mid-task).');
278
+
279
+ skillCmd
280
+ .command('list', { isDefault: true })
281
+ .description('List every available skill.')
282
+ .option('-a, --all', 'include full descriptions (default shows a compact table)')
283
+ .action((opts) => {
284
+ const skills = require('../lib/skills');
285
+ const list = skills.listSkills();
286
+ if (list.length === 0) {
287
+ console.log('No skills installed.');
288
+ return;
289
+ }
290
+ const daily = list.filter((s) => s.daily);
291
+ const rest = list.filter((s) => !s.daily);
292
+ console.log(c.bold(`${list.length} skills`) + c.muted(` (${daily.length} loaded by default, ${rest.length} available via list_skills or /skill install)`));
293
+ console.log('');
294
+ for (const s of list) {
295
+ const tag = s.source === 'user' ? c.green('[user]') : s.daily ? c.blue('[daily]') : c.muted('[library]');
296
+ console.log(`${s.name.padEnd(30)} ${tag}`);
297
+ if (opts.all) console.log(c.muted(` ${s.description}`));
298
+ }
299
+ console.log('');
300
+ console.log(c.muted(`Bundled from affaan-m/ECC — see skills/SOURCE.md. Add your own: codeply skill install <path-or-github-url>`));
301
+ });
302
+
303
+ skillCmd
304
+ .command('install <source>')
305
+ .description('Install a skill from a local directory (containing SKILL.md) or a GitHub URL/owner-repo.')
306
+ .option('-n, --name <name>', 'name to install under (single-skill sources only; ignored for a whole-repo install)')
307
+ .action(async (source, opts) => {
308
+ const skills = require('../lib/skills');
309
+ const isLocal = fs.existsSync(source);
310
+
311
+ if (isLocal) {
312
+ const result = skills.installFromLocalDir(path.resolve(process.cwd(), source), opts.name);
313
+ if (!result.ok) { console.error(c.red(result.error)); process.exitCode = 1; return; }
314
+ console.log(c.green(`✓ Installed "${result.name}"`));
315
+ console.log(c.muted(` ${result.dest}`));
316
+ return;
317
+ }
318
+
319
+ console.log(c.secondary(`Fetching from ${source}…`));
320
+ let result;
321
+ try {
322
+ result = await skills.installFromGitHub(source, opts.name);
323
+ } catch (e) {
324
+ console.error(c.red(`Install failed: ${e.message}`));
325
+ process.exitCode = 1;
326
+ return;
327
+ }
328
+ if (!result.ok) {
329
+ console.error(c.red(result.error || `No skills installed from ${source}.`));
330
+ process.exitCode = 1;
331
+ return;
332
+ }
333
+ console.log(c.green(`✓ Installed ${result.installed.length} skill(s) from ${result.source}`));
334
+ for (const i of result.installed) console.log(c.muted(` ${i.name} (${i.fileCount} file(s)) → ${i.dest}`));
335
+ if (result.failed.length) {
336
+ console.log(c.yellow(` ${result.failed.length} skipped (fetch failed):`));
337
+ for (const f of result.failed) console.log(c.muted(` ${f.dir}: ${f.error}`));
338
+ }
339
+ });
340
+
341
+ skillCmd
342
+ .command('remove <name>')
343
+ .description('Remove a user-installed skill. Bundled skills cannot be removed this way.')
344
+ .action((name) => {
345
+ const skills = require('../lib/skills');
346
+ const result = skills.removeSkill(name);
347
+ if (!result.ok) { console.error(c.red(result.error)); process.exitCode = 1; return; }
348
+ console.log(c.green(`✓ Removed "${name}"`));
349
+ });
350
+
351
+ skillCmd
352
+ .command('show <name>')
353
+ .description('Print a skill\'s full instructions.')
354
+ .action((name) => {
355
+ const skills = require('../lib/skills');
356
+ const body = skills.loadSkillBody(name);
357
+ if (body == null) {
358
+ console.error(c.red(`No skill named "${name}". Run \`codeply skill list\` to see what's available.`));
359
+ process.exitCode = 1;
360
+ return;
361
+ }
362
+ console.log(body);
363
+ });
364
+
254
365
  // If no arguments provided, launch TUI
255
366
  if (process.argv.length <= 2) {
256
367
  runTUI();
package/lib/agent.mjs CHANGED
@@ -14,10 +14,11 @@ import fs from 'fs';
14
14
  import path from 'path';
15
15
  import { execSync } from 'child_process';
16
16
  import { createRequire } from 'module';
17
- import { executeTool, TOOL_NEEDS_APPROVAL, walkFiles } from './tools.mjs';
17
+ import { executeTool, TOOL_NEEDS_APPROVAL, walkFiles, resolvePath } from './tools.mjs';
18
18
 
19
19
  const require = createRequire(import.meta.url);
20
20
  const ai = require('./ai.js');
21
+ const skills = require('./skills.js');
21
22
 
22
23
  const MAX_STEPS = 24;
23
24
  const MAX_MALFORMED_RETRIES = 3;
@@ -31,7 +32,16 @@ const FORMAT_CORRECTION =
31
32
  'do not drop the "codeply:" prefix, and keep the underscore in the name. Write it exactly like this, ' +
32
33
  'opening and closing tags complete:\n\n' +
33
34
  '<codeply:read_file>\n<path>run.html</path>\n</codeply:read_file>\n\n' +
34
- 'Valid names: list_dir, read_file, write_file, edit_file, search, run.';
35
+ 'Valid names: list_dir, read_file, write_file, edit_file, search, run, use_skill, list_skills.';
36
+
37
+ const TRUNCATION_CORRECTION =
38
+ '[system] That reply got cut off partway through the action block — the tag syntax was fine, it simply ran out of ' +
39
+ 'room before the block closed. This happens when write_file or edit_file tries to carry too much content in one reply. ' +
40
+ "Don't re-explain or apologize for the format, just write less this time: for a new or rewritten file, write_file only " +
41
+ 'the first section now and use edit_file to add the rest over one or two more steps; for an edit, split it into smaller ' +
42
+ 'search/replace pairs. Try again with a smaller block.';
43
+
44
+ const MAX_TRUNCATED_RETRIES = 3;
35
45
 
36
46
  // ─── Protocol ───────────────────────────────────────────────────────────────
37
47
 
@@ -88,6 +98,8 @@ const PARAMS = {
88
98
  edit_file: ['path', 'search', 'replace'],
89
99
  search: ['pattern', 'glob', 'path'],
90
100
  run: ['command'],
101
+ use_skill: ['name'],
102
+ list_skills: ['query'],
91
103
  };
92
104
 
93
105
  // Names the model actually reaches for when it paraphrases the format.
@@ -104,6 +116,8 @@ const NAME_ALIASES = {
104
116
  edit_file: 'edit_file', editfile: 'edit_file', edit: 'edit_file',
105
117
  grep: 'search', findtext: 'search',
106
118
  run: 'run', bash: 'run', shell: 'run', exec: 'run', runcommand: 'run',
119
+ use_skill: 'use_skill', useskill: 'use_skill', skill: 'use_skill', loadskill: 'use_skill',
120
+ list_skills: 'list_skills', listskills: 'list_skills', skills: 'list_skills', findskill: 'list_skills',
107
121
  };
108
122
 
109
123
  // Any tag whose name resolves to an action, with or without the codeply: prefix.
@@ -113,7 +127,21 @@ const LOOSE_NAME_TAG = new RegExp(
113
127
 
114
128
  // Does this reply look like it was *trying* to act, even though nothing parsed?
115
129
  const LOOKS_LIKE_ATTEMPT =
116
- /<\/?\s*(?:codeply[:_-])?(?:tool_?call|action|function_?call|invoke)\b|<(?:path|command|pattern|content)>/i;
130
+ /<\/?\s*(?:codeply[:_-])?(?:tool_?call|action|function_?call|invoke)\b|<(?:path|command|pattern|content|name)>/i;
131
+
132
+ /**
133
+ * A reply that opens an action block but never closes it almost always means
134
+ * the model hit its response length limit mid-write (a full-file write_file
135
+ * on anything but a small file is the usual trigger) rather than that it
136
+ * wrote a malformed tag. That distinction matters: the generic format
137
+ * correction tells the model its *syntax* was wrong, which it wasn't, and
138
+ * the model then apologizes for a mistake it didn't make and repeats the
139
+ * same oversized write. Detecting this case lets the correction instead say
140
+ * "you ran out of room, write less at once" — the thing that actually fixes it.
141
+ */
142
+ function looksTruncated(text) {
143
+ return /<codeply:([a-z_]+)>(?![\s\S]*<\/codeply:\1>)/i.test(text);
144
+ }
117
145
 
118
146
  /**
119
147
  * Best-effort recovery when the strict protocol did not match.
@@ -137,6 +165,8 @@ function recoverCall(text) {
137
165
  if (!name && has('command')) name = 'run';
138
166
  else if (!name && has('pattern')) name = 'search';
139
167
  else if (!name && has('path')) name = 'read_file';
168
+ else if (!name && has('name')) name = 'use_skill'; // only use_skill's params include <name>
169
+ else if (!name && has('query')) name = 'list_skills'; // only list_skills' params include <query>
140
170
  }
141
171
  if (!name || !PARAMS[name]) return null;
142
172
 
@@ -172,24 +202,32 @@ export function parseReply(text) {
172
202
  prose += text.slice(lastIndex);
173
203
 
174
204
  if (calls.length > 0) {
175
- return { prose: prose.trim(), calls, recovered: false, malformed: false };
205
+ return { prose: prose.trim(), calls, recovered: false, malformed: false, truncated: false };
176
206
  }
177
207
 
178
- const salvaged = recoverCall(text);
179
- if (salvaged) {
180
- // Strip the mangled markup so the user reads the intent, not the debris.
181
- const cleaned = text
182
- .replace(/<\/?[a-z_:]+>/gi, ' ')
183
- .replace(/[ \t]{2,}/g, ' ')
184
- .trim();
185
- return { prose: cleaned, calls: [salvaged], recovered: true, malformed: false };
208
+ const truncated = looksTruncated(text);
209
+
210
+ // Don't let the salvage path guess at a call built from a truncated block —
211
+ // it would be missing content/search/replace and either fail oddly or write
212
+ // a partial file. Truncation gets its own, more accurate correction instead.
213
+ if (!truncated) {
214
+ const salvaged = recoverCall(text);
215
+ if (salvaged) {
216
+ // Strip the mangled markup so the user reads the intent, not the debris.
217
+ const cleaned = text
218
+ .replace(/<\/?[a-z_:]+>/gi, ' ')
219
+ .replace(/[ \t]{2,}/g, ' ')
220
+ .trim();
221
+ return { prose: cleaned, calls: [salvaged], recovered: true, malformed: false, truncated: false };
222
+ }
186
223
  }
187
224
 
188
225
  return {
189
226
  prose: prose.trim(),
190
227
  calls: [],
191
228
  recovered: false,
192
- malformed: LOOKS_LIKE_ATTEMPT.test(text),
229
+ malformed: !truncated && LOOKS_LIKE_ATTEMPT.test(text),
230
+ truncated,
193
231
  };
194
232
  }
195
233
 
@@ -286,6 +324,14 @@ those lines with the change applied
286
324
  <command>node --check index.js</command>
287
325
  </codeply:run>
288
326
 
327
+ <codeply:use_skill>
328
+ <name>deep-research</name>
329
+ </codeply:use_skill>
330
+
331
+ <codeply:list_skills>
332
+ <query>testing</query>
333
+ </codeply:list_skills>
334
+
289
335
  RULES
290
336
  - Write at most ONE action block per reply, then stop and wait for its result.
291
337
  - Do not waste steps. Every action block costs the user several seconds, so never
@@ -298,6 +344,8 @@ RULES
298
344
  - read_file before you edit_file. "search" must be copied verbatim from what you just read — never from memory, never with the line-number prefixes the reader adds.
299
345
  - edit_file for changes to an existing file. write_file only for new files or a genuine full rewrite.
300
346
  - Prefer run for anything you can check mechanically.
347
+ - If a SKILLS entry below is a clear match for the task, use_skill it before starting — its instructions take priority over your own default approach for that kind of work. Do not use_skill speculatively; only when a listed skill actually matches what you are about to do.
348
+ - The SKILLS list below is a subset. If the task is a specific kind of specialized work (a particular framework, a particular deliverable type) that doesn't clearly match anything listed, try list_skills with a one- or two-word query before assuming there's no skill for it — there are 281 in total, not just the ones shown.
301
349
  - When the task is finished, reply with prose only and NO action block. That ends your turn.`;
302
350
 
303
351
  const VERIFY_RULES = `VERIFYING YOUR WORK
@@ -338,11 +386,30 @@ Be concise and concrete. Quote the relevant code and cite it as path:line. If th
338
386
 
339
387
  const READ_ONLY_MODES = new Set(['Plan', 'Ask']);
340
388
 
389
+ /**
390
+ * Skills add capability without adding weight proportional to the whole
391
+ * library: only a curated subset's name + capped description goes in here —
392
+ * see lib/skills.js for why the bundled set (281 skills, ~3.4MB) can't be
393
+ * injected in full on every request without the skill library itself becoming
394
+ * the majority of the token cost. The rest is one list_skills query away.
395
+ */
396
+ function buildSkillIndex() {
397
+ const all = skills.listSkills();
398
+ const daily = skills.formatSkillIndex(all);
399
+ if (!daily) return null;
400
+ const rest = all.length - all.filter((s) => s.daily).length;
401
+ return `SKILLS\nOptional playbooks for specific kinds of work. Load one with use_skill ` +
402
+ `when its description clearly matches the current task. This is a subset — ` +
403
+ `${rest} more exist; search them with list_skills if nothing here fits:\n${daily}`;
404
+ }
405
+
341
406
  export function buildSystemPrompt(mode, cwd) {
342
407
  const parts = [MODE_PROMPT[mode] || MODE_PROMPT.Build, '', TOOL_REFERENCE];
343
408
  if (READ_ONLY_MODES.has(mode)) {
344
409
  parts.push('', 'MODE RESTRICTION: write_file and edit_file are disabled. Using them returns an error. run is limited to read-only commands.');
345
410
  }
411
+ const skillIndex = buildSkillIndex();
412
+ if (skillIndex) parts.push('', skillIndex);
346
413
  parts.push('', 'PROJECT CONTEXT', buildProjectContext(cwd));
347
414
  return parts.join('\n');
348
415
  }
@@ -389,12 +456,13 @@ const NO_NATIVE_TOOLS_REMINDER =
389
456
  '[system] Your last reply tried to invoke a function through the API. That is not available here and the response was discarded. ' +
390
457
  'Write the action block as literal text in your message — the tags exactly as shown in the OUTPUT FORMAT section — and nothing else.';
391
458
 
392
- async function callModel(messages) {
393
- let result = await ai.chat(messages);
459
+ async function callModel(messages, promptText) {
460
+ const meta = promptText ? { promptText } : undefined;
461
+ let result = await ai.chat(messages, { meta });
394
462
  if (result.success || !isNativeToolCallError(result.error)) return { result, messages };
395
463
 
396
464
  const corrected = [...messages, { role: 'user', content: NO_NATIVE_TOOLS_REMINDER }];
397
- result = await ai.chat(corrected);
465
+ result = await ai.chat(corrected, { meta });
398
466
  return { result, messages: corrected };
399
467
  }
400
468
 
@@ -423,11 +491,33 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
423
491
  const ctx = { cwd, approve };
424
492
  const transcript = [{ role: 'user', content: userMessage }];
425
493
  let malformedRetries = 0;
494
+ let truncatedRetries = 0;
495
+
496
+ // Two mechanisms that cut the actual token COUNT sent, not just its billed
497
+ // price (prompt caching, elsewhere in ai.js, only does the latter):
498
+ //
499
+ // - servedCalls: a repeat read_file/list_dir/search/use_skill/list_skills
500
+ // call within one turn — same tool, same arguments — is a real, common
501
+ // pattern (the model re-checks something it already has). Re-running it
502
+ // would resend the exact same content a second time for zero new
503
+ // information; a short pointer back to the earlier result carries the
504
+ // same information in a fraction of the tokens.
505
+ // - readIndexByPath: once a file is edited, any EARLIER read_file result
506
+ // for that same path in the transcript no longer describes what's on
507
+ // disk. Besides being a real correctness hazard (the model reasoning off
508
+ // stale content), keeping it around costs tokens for information that is
509
+ // now wrong. It gets collapsed to a pointer the moment the edit lands,
510
+ // not deferred to trimTranscript()'s budget-driven pass.
511
+ const DEDUPABLE = new Set(['read_file', 'list_dir', 'search', 'use_skill', 'list_skills']);
512
+ const servedCalls = new Map(); // callKey -> message index of its result
513
+ const readIndexByPath = new Map(); // resolved path -> message index of its read_file result
514
+
515
+ const callKey = (name, args) => `${name}:${JSON.stringify(args)}`;
426
516
 
427
517
  for (let step = 0; step < MAX_STEPS; step++) {
428
518
  if (signal.aborted) { yield { type: 'aborted' }; return; }
429
519
 
430
- const attempt = await callModel(trimTranscript(messages));
520
+ const attempt = await callModel(trimTranscript(messages), userMessage);
431
521
  const result = attempt.result;
432
522
  if (!result.success) {
433
523
  yield {
@@ -454,7 +544,7 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
454
544
  }
455
545
  messages.push({ role: 'assistant', content: reply });
456
546
 
457
- const { prose, calls, recovered, malformed } = parseReply(reply);
547
+ const { prose, calls, recovered, malformed, truncated } = parseReply(reply);
458
548
  if (prose) {
459
549
  yield { type: 'text', text: prose };
460
550
  transcript.push({ role: 'assistant', content: prose });
@@ -464,6 +554,11 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
464
554
  // A reply that was clearly *reaching* for an action but came out
465
555
  // unparseable must not be mistaken for "finished" — that silently drops
466
556
  // the user's task. Correct it and let it try again.
557
+ if (truncated && truncatedRetries < MAX_TRUNCATED_RETRIES) {
558
+ truncatedRetries++;
559
+ messages.push({ role: 'user', content: TRUNCATION_CORRECTION });
560
+ continue;
561
+ }
467
562
  if (malformed && malformedRetries < MAX_MALFORMED_RETRIES) {
468
563
  malformedRetries++;
469
564
  messages.push({ role: 'user', content: FORMAT_CORRECTION });
@@ -498,6 +593,23 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
498
593
  continue;
499
594
  }
500
595
 
596
+ // A repeat of an earlier read-only call, same tool and same arguments,
597
+ // carries zero new information — reuse the earlier result instead of
598
+ // spending tokens to resend it. This is skipped for read_file specifically
599
+ // when the file was edited since (see the invalidation below): the whole
600
+ // point there is to force a fresh read, not to serve stale content.
601
+ if (DEDUPABLE.has(call.name) && servedCalls.has(callKey(call.name, call.args))) {
602
+ messages.push({
603
+ role: 'user',
604
+ content: `[tool result: ${call.name}] Unchanged since your earlier identical call — reusing that result, not re-run. Nothing new to see; move on.`,
605
+ });
606
+ yield {
607
+ type: 'tool_end', name: call.name, args: call.args, ok: true,
608
+ meta: { deduped: true }, summary: 'unchanged — reused earlier result',
609
+ };
610
+ continue;
611
+ }
612
+
501
613
  yield { type: 'tool_start', name: call.name, args: call.args };
502
614
 
503
615
  const needsApproval = TOOL_NEEDS_APPROVAL.has(call.name);
@@ -518,6 +630,33 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
518
630
 
519
631
  messages.push({ role: 'user', content: `[tool result: ${call.name}]\n${out.output}` });
520
632
 
633
+ if (out.ok) {
634
+ if (DEDUPABLE.has(call.name)) {
635
+ servedCalls.set(callKey(call.name, call.args), messages.length - 1);
636
+ }
637
+ if (call.name === 'read_file' && call.args.path) {
638
+ const abs = resolvePath(call.args.path, cwd).abs;
639
+ readIndexByPath.set(abs, { msgIndex: messages.length - 1, key: callKey(call.name, call.args) });
640
+ }
641
+ if ((call.name === 'edit_file' || call.name === 'write_file') && call.args.path) {
642
+ // The file just changed, so any earlier read_file result for it is now
643
+ // wrong, not just old — collapse it in place rather than leaving
644
+ // outdated content sitting in the transcript for the model to
645
+ // (mis)reason from, and drop it from the dedup cache so a genuinely
646
+ // fresh read_file after this point is not short-circuited by it.
647
+ const abs = resolvePath(call.args.path, cwd).abs;
648
+ const stale = readIndexByPath.get(abs);
649
+ if (stale) {
650
+ messages[stale.msgIndex] = {
651
+ role: 'user',
652
+ content: `[tool result: read_file] ${call.args.path} — superseded by the ${call.name} below; that earlier content no longer matches the file. Re-read if you need to see it again.`,
653
+ };
654
+ servedCalls.delete(stale.key);
655
+ readIndexByPath.delete(abs);
656
+ }
657
+ }
658
+ }
659
+
521
660
  // A declined action is the user's answer, not a retry prompt.
522
661
  if (out.meta?.rejected) {
523
662
  messages.push({