codeply-cli 0.1.1 → 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.
- package/bin/codeply.js +125 -14
- package/lib/agent.mjs +152 -14
- package/lib/ai.js +217 -14
- package/lib/apply-limit.js +13 -3
- package/lib/config.js +53 -15
- package/lib/skills.js +358 -0
- package/lib/tools.mjs +63 -6
- package/lib/tui.mjs +95 -2
- package/package.json +3 -2
- package/skills/SOURCE.md +58 -0
- package/skills/accessibility/SKILL.md +147 -0
- package/skills/agent-architecture-audit/SKILL.md +257 -0
- package/skills/agent-eval/SKILL.md +147 -0
- package/skills/agent-harness-construction/SKILL.md +74 -0
- package/skills/agent-introspection-debugging/SKILL.md +154 -0
- package/skills/agent-payment-x402/SKILL.md +225 -0
- package/skills/agent-self-evaluation/SKILL.md +182 -0
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -0
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -0
- package/skills/agent-sort/SKILL.md +216 -0
- package/skills/agentic-engineering/SKILL.md +64 -0
- package/skills/agentic-os/SKILL.md +388 -0
- package/skills/ai-first-engineering/SKILL.md +52 -0
- package/skills/ai-regression-testing/SKILL.md +386 -0
- package/skills/android-clean-architecture/SKILL.md +340 -0
- package/skills/angular-developer/SKILL.md +155 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +410 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +59 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/skills/angular-developer/references/e2e-testing.md +56 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/mcp.md +108 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +122 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +77 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +795 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +65 -0
- package/skills/api-connector-builder/SKILL.md +121 -0
- package/skills/api-design/SKILL.md +524 -0
- package/skills/architecture-decision-records/SKILL.md +180 -0
- package/skills/article-writing/SKILL.md +80 -0
- package/skills/automation-audit-ops/SKILL.md +143 -0
- package/skills/autonomous-agent-harness/SKILL.md +274 -0
- package/skills/autonomous-loops/SKILL.md +611 -0
- package/skills/backend-patterns/SKILL.md +562 -0
- package/skills/benchmark/SKILL.md +95 -0
- package/skills/benchmark-methodology/SKILL.md +191 -0
- package/skills/benchmark-optimization-loop/SKILL.md +71 -0
- package/skills/blender-motion-state-inspection/SKILL.md +165 -0
- package/skills/blueprint/SKILL.md +106 -0
- package/skills/brand-discovery/SKILL.md +145 -0
- package/skills/brand-discovery/references/10_purpose-why.md +40 -0
- package/skills/brand-discovery/references/20_positioning.md +44 -0
- package/skills/brand-discovery/references/30_audience-niche.md +52 -0
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -0
- package/skills/brand-discovery/references/50_voice-tone.md +59 -0
- package/skills/brand-discovery/references/60_narrative-story.md +50 -0
- package/skills/brand-discovery/references/70_founder-tension.md +49 -0
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -0
- package/skills/brand-voice/SKILL.md +98 -0
- package/skills/brand-voice/references/voice-profile-schema.md +55 -0
- package/skills/browser-qa/SKILL.md +105 -0
- package/skills/bun-runtime/SKILL.md +85 -0
- package/skills/canary-watch/SKILL.md +108 -0
- package/skills/carrier-relationship-management/SKILL.md +212 -0
- package/skills/cisco-ios-patterns/SKILL.md +164 -0
- package/skills/ck/SKILL.md +148 -0
- package/skills/claude-devfleet/SKILL.md +112 -0
- package/skills/click-path-audit/SKILL.md +245 -0
- package/skills/clickhouse-io/SKILL.md +445 -0
- package/skills/code-tour/SKILL.md +254 -0
- package/skills/codebase-onboarding/SKILL.md +234 -0
- package/skills/codehealth-mcp/SKILL.md +167 -0
- package/skills/coding-standards/SKILL.md +551 -0
- package/skills/competitive-platform-analysis/SKILL.md +214 -0
- package/skills/competitive-report-structure/SKILL.md +162 -0
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -0
- package/skills/config-gc/SKILL.md +120 -0
- package/skills/configure-ecc/SKILL.md +385 -0
- package/skills/connections-optimizer/SKILL.md +190 -0
- package/skills/content-engine/SKILL.md +132 -0
- package/skills/content-hash-cache-pattern/SKILL.md +162 -0
- package/skills/context-budget/SKILL.md +136 -0
- package/skills/continuous-agent-loop/SKILL.md +46 -0
- package/skills/continuous-learning/SKILL.md +132 -0
- package/skills/continuous-learning-v2/SKILL.md +361 -0
- package/skills/contract-first/SKILL.md +287 -0
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -0
- package/skills/cost-tracking/SKILL.md +97 -0
- package/skills/council/SKILL.md +204 -0
- package/skills/cpp-coding-standards/SKILL.md +724 -0
- package/skills/cpp-testing/SKILL.md +325 -0
- package/skills/crosspost/SKILL.md +112 -0
- package/skills/csharp-testing/SKILL.md +322 -0
- package/skills/customer-billing-ops/SKILL.md +141 -0
- package/skills/customs-trade-compliance/SKILL.md +263 -0
- package/skills/dart-flutter-patterns/SKILL.md +564 -0
- package/skills/dashboard-builder/SKILL.md +109 -0
- package/skills/data-scraper-agent/SKILL.md +765 -0
- package/skills/data-throughput-accelerator/SKILL.md +74 -0
- package/skills/database-migrations/SKILL.md +430 -0
- package/skills/deep-research/SKILL.md +160 -0
- package/skills/defi-amm-security/SKILL.md +167 -0
- package/skills/delivery-gate/SKILL.md +126 -0
- package/skills/deployment-patterns/SKILL.md +428 -0
- package/skills/design-system/SKILL.md +83 -0
- package/skills/django-celery/SKILL.md +458 -0
- package/skills/django-patterns/SKILL.md +735 -0
- package/skills/django-security/SKILL.md +644 -0
- package/skills/django-tdd/SKILL.md +730 -0
- package/skills/django-verification/SKILL.md +470 -0
- package/skills/dmux-workflows/SKILL.md +192 -0
- package/skills/docker-patterns/SKILL.md +365 -0
- package/skills/documentation-lookup/SKILL.md +91 -0
- package/skills/dotnet-patterns/SKILL.md +322 -0
- package/skills/dynamic-workflow-mode/SKILL.md +124 -0
- package/skills/e2e-testing/SKILL.md +327 -0
- package/skills/ecc-guide/SKILL.md +190 -0
- package/skills/ecc-recipes/SKILL.md +149 -0
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -0
- package/skills/email-ops/SKILL.md +122 -0
- package/skills/energy-procurement/SKILL.md +228 -0
- package/skills/enterprise-agent-ops/SKILL.md +51 -0
- package/skills/error-handling/SKILL.md +377 -0
- package/skills/eval-harness/SKILL.md +271 -0
- package/skills/evm-token-decimals/SKILL.md +131 -0
- package/skills/exa-search/SKILL.md +108 -0
- package/skills/fal-ai-media/SKILL.md +289 -0
- package/skills/fastapi-patterns/SKILL.md +514 -0
- package/skills/finance-billing-ops/SKILL.md +128 -0
- package/skills/flox-environments/SKILL.md +497 -0
- package/skills/flutter-dart-code-review/SKILL.md +436 -0
- package/skills/foundation-models-on-device/SKILL.md +243 -0
- package/skills/frontend-a11y/SKILL.md +446 -0
- package/skills/frontend-design-direction/SKILL.md +93 -0
- package/skills/frontend-patterns/SKILL.md +657 -0
- package/skills/frontend-slides/SKILL.md +185 -0
- package/skills/fsharp-testing/SKILL.md +281 -0
- package/skills/gan-style-harness/SKILL.md +279 -0
- package/skills/gateguard/SKILL.md +133 -0
- package/skills/generating-python-installer/SKILL.md +820 -0
- package/skills/git-workflow/SKILL.md +716 -0
- package/skills/github-ops/SKILL.md +145 -0
- package/skills/golang-patterns/SKILL.md +676 -0
- package/skills/golang-testing/SKILL.md +721 -0
- package/skills/google-workspace-ops/SKILL.md +96 -0
- package/skills/growth-log/SKILL.md +128 -0
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -0
- package/skills/healthcare-emr-patterns/SKILL.md +160 -0
- package/skills/healthcare-eval-harness/SKILL.md +208 -0
- package/skills/healthcare-phi-compliance/SKILL.md +146 -0
- package/skills/hermes-imports/SKILL.md +89 -0
- package/skills/hexagonal-architecture/SKILL.md +277 -0
- package/skills/hipaa-compliance/SKILL.md +79 -0
- package/skills/homelab-network-readiness/SKILL.md +170 -0
- package/skills/homelab-network-setup/SKILL.md +130 -0
- package/skills/homelab-pihole-dns/SKILL.md +275 -0
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -0
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -0
- package/skills/hookify-rules/SKILL.md +128 -0
- package/skills/inherit-legacy-style/SKILL.md +157 -0
- package/skills/intent-driven-development/SKILL.md +360 -0
- package/skills/inventory-demand-planning/SKILL.md +247 -0
- package/skills/investor-materials/SKILL.md +97 -0
- package/skills/investor-outreach/SKILL.md +92 -0
- package/skills/ios-icon-gen/SKILL.md +158 -0
- package/skills/iterative-retrieval/SKILL.md +212 -0
- package/skills/ito-basket-compare/SKILL.md +64 -0
- package/skills/ito-compute/SKILL.md +129 -0
- package/skills/ito-data-atlas-agent/SKILL.md +64 -0
- package/skills/ito-market-intelligence/SKILL.md +61 -0
- package/skills/ito-trade-planner/SKILL.md +68 -0
- package/skills/java-coding-standards/SKILL.md +384 -0
- package/skills/jira-integration/SKILL.md +303 -0
- package/skills/jpa-patterns/SKILL.md +152 -0
- package/skills/knowledge-ops/SKILL.md +155 -0
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -0
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -0
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -0
- package/skills/kotlin-patterns/SKILL.md +712 -0
- package/skills/kotlin-testing/SKILL.md +825 -0
- package/skills/kubernetes-patterns/SKILL.md +756 -0
- package/skills/laravel-patterns/SKILL.md +416 -0
- package/skills/laravel-plugin-discovery/SKILL.md +230 -0
- package/skills/laravel-security/SKILL.md +948 -0
- package/skills/laravel-tdd/SKILL.md +675 -0
- package/skills/laravel-verification/SKILL.md +180 -0
- package/skills/latency-critical-systems/SKILL.md +75 -0
- package/skills/lead-intelligence/SKILL.md +322 -0
- package/skills/liquid-glass-design/SKILL.md +279 -0
- package/skills/llm-trading-agent-security/SKILL.md +147 -0
- package/skills/logistics-exception-management/SKILL.md +222 -0
- package/skills/loop-design-check/SKILL.md +143 -0
- package/skills/mailtrap-email-integration/SKILL.md +77 -0
- package/skills/make-interfaces-feel-better/SKILL.md +152 -0
- package/skills/manim-video/SKILL.md +90 -0
- package/skills/market-research/SKILL.md +76 -0
- package/skills/marketing-campaign/SKILL.md +114 -0
- package/skills/mcp-server-patterns/SKILL.md +70 -0
- package/skills/messages-ops/SKILL.md +105 -0
- package/skills/ml-adoption-playbook/SKILL.md +57 -0
- package/skills/mle-workflow/SKILL.md +348 -0
- package/skills/motion-advanced/SKILL.md +596 -0
- package/skills/motion-foundations/SKILL.md +299 -0
- package/skills/motion-patterns/SKILL.md +434 -0
- package/skills/motion-ui/SKILL.md +576 -0
- package/skills/mysql-patterns/SKILL.md +413 -0
- package/skills/nanoclaw-repl/SKILL.md +34 -0
- package/skills/nestjs-patterns/SKILL.md +231 -0
- package/skills/netmiko-ssh-automation/SKILL.md +174 -0
- package/skills/network-bgp-diagnostics/SKILL.md +168 -0
- package/skills/network-config-validation/SKILL.md +211 -0
- package/skills/network-interface-health/SKILL.md +153 -0
- package/skills/nextjs-turbopack/SKILL.md +58 -0
- package/skills/nodejs-keccak256/SKILL.md +103 -0
- package/skills/nutrient-document-processing/SKILL.md +168 -0
- package/skills/nuxt4-patterns/SKILL.md +101 -0
- package/skills/openclaw-persona-forge/SKILL.md +289 -0
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -0
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -0
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -0
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -0
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -0
- package/skills/openclaw-persona-forge/references/output-template.md +166 -0
- package/skills/opensource-pipeline/SKILL.md +256 -0
- package/skills/orch-add-feature/SKILL.md +45 -0
- package/skills/orch-build-mvp/SKILL.md +49 -0
- package/skills/orch-change-feature/SKILL.md +43 -0
- package/skills/orch-fix-defect/SKILL.md +43 -0
- package/skills/orch-pipeline/SKILL.md +121 -0
- package/skills/orch-refine-code/SKILL.md +44 -0
- package/skills/parallel-execution-optimizer/SKILL.md +74 -0
- package/skills/perl-patterns/SKILL.md +505 -0
- package/skills/perl-security/SKILL.md +504 -0
- package/skills/perl-testing/SKILL.md +476 -0
- package/skills/plan-canvas/SKILL.md +153 -0
- package/skills/plan-orchestrate/SKILL.md +263 -0
- package/skills/plankton-code-quality/SKILL.md +237 -0
- package/skills/postgres-patterns/SKILL.md +148 -0
- package/skills/prediction-market-oracle-research/SKILL.md +64 -0
- package/skills/prediction-market-risk-review/SKILL.md +61 -0
- package/skills/prisma-patterns/SKILL.md +401 -0
- package/skills/product-capability/SKILL.md +142 -0
- package/skills/product-lens/SKILL.md +93 -0
- package/skills/production-audit/SKILL.md +207 -0
- package/skills/production-scheduling/SKILL.md +238 -0
- package/skills/project-flow-ops/SKILL.md +112 -0
- package/skills/prompt-optimizer/SKILL.md +398 -0
- package/skills/python-patterns/SKILL.md +751 -0
- package/skills/python-testing/SKILL.md +817 -0
- package/skills/pytorch-patterns/SKILL.md +397 -0
- package/skills/quality-nonconformance/SKILL.md +260 -0
- package/skills/quarkus-patterns/SKILL.md +723 -0
- package/skills/quarkus-security/SKILL.md +468 -0
- package/skills/quarkus-tdd/SKILL.md +812 -0
- package/skills/quarkus-verification/SKILL.md +480 -0
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -0
- package/skills/react-native-patterns/SKILL.md +326 -0
- package/skills/react-patterns/SKILL.md +342 -0
- package/skills/react-performance/SKILL.md +575 -0
- package/skills/react-testing/SKILL.md +424 -0
- package/skills/recsys-pipeline-architect/SKILL.md +115 -0
- package/skills/recursive-decision-ledger/SKILL.md +81 -0
- package/skills/redis-patterns/SKILL.md +404 -0
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -0
- package/skills/remotion-video-creation/SKILL.md +43 -0
- package/skills/repo-scan/SKILL.md +79 -0
- package/skills/research-ops/SKILL.md +113 -0
- package/skills/returns-reverse-logistics/SKILL.md +240 -0
- package/skills/rules-distill/SKILL.md +265 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/safety-guard/SKILL.md +76 -0
- package/skills/santa-method/SKILL.md +307 -0
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -0
- package/skills/scientific-db-uspto-database/SKILL.md +178 -0
- package/skills/scientific-pkg-gget/SKILL.md +167 -0
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -0
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -0
- package/skills/search-first/SKILL.md +183 -0
- package/skills/security-bounty-hunter/SKILL.md +100 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-scan/SKILL.md +166 -0
- package/skills/seo/SKILL.md +155 -0
- package/skills/skill-comply/SKILL.md +59 -0
- package/skills/skill-scout/SKILL.md +141 -0
- package/skills/skill-stocktake/SKILL.md +195 -0
- package/skills/social-graph-ranker/SKILL.md +155 -0
- package/skills/social-publisher/SKILL.md +130 -0
- package/skills/springboot-patterns/SKILL.md +315 -0
- package/skills/springboot-security/SKILL.md +273 -0
- package/skills/springboot-tdd/SKILL.md +159 -0
- package/skills/springboot-verification/SKILL.md +232 -0
- package/skills/strategic-compact/SKILL.md +142 -0
- package/skills/swift-actor-persistence/SKILL.md +144 -0
- package/skills/swift-concurrency-6-2/SKILL.md +216 -0
- package/skills/swift-protocol-di-testing/SKILL.md +191 -0
- package/skills/swiftui-patterns/SKILL.md +259 -0
- package/skills/taste/SKILL.md +264 -0
- package/skills/taste/references/genre-taxonomy.md +87 -0
- package/skills/tdd-workflow/SKILL.md +583 -0
- package/skills/team-agent-orchestration/SKILL.md +111 -0
- package/skills/team-builder/SKILL.md +169 -0
- package/skills/terminal-ops/SKILL.md +110 -0
- package/skills/tinystruct-patterns/SKILL.md +279 -0
- package/skills/tinystruct-patterns/references/architecture.md +90 -0
- package/skills/tinystruct-patterns/references/data-handling.md +60 -0
- package/skills/tinystruct-patterns/references/database.md +99 -0
- package/skills/tinystruct-patterns/references/routing.md +64 -0
- package/skills/tinystruct-patterns/references/system-usage.md +97 -0
- package/skills/tinystruct-patterns/references/testing.md +72 -0
- package/skills/token-budget-advisor/SKILL.md +134 -0
- package/skills/ui-demo/SKILL.md +466 -0
- package/skills/ui-to-vue/SKILL.md +135 -0
- package/skills/uncloud/SKILL.md +344 -0
- package/skills/unified-memory/SKILL.md +170 -0
- package/skills/unified-notifications-ops/SKILL.md +188 -0
- package/skills/verification-loop/SKILL.md +129 -0
- package/skills/video-editing/SKILL.md +311 -0
- package/skills/videodb/SKILL.md +375 -0
- package/skills/visa-doc-translate/SKILL.md +117 -0
- package/skills/vite-patterns/SKILL.md +450 -0
- package/skills/vue-patterns/SKILL.md +471 -0
- package/skills/windows-desktop-e2e/SKILL.md +888 -0
- package/skills/workspace-surface-audit/SKILL.md +126 -0
- 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)
|
|
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>', '
|
|
203
|
-
.option('--model <model>', '
|
|
204
|
-
.option('--host <url>', '
|
|
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
|
|
222
|
-
console.error(`Unknown provider "${name}". Use
|
|
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
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
}
|
|
@@ -424,6 +491,28 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
|
|
|
424
491
|
const ctx = { cwd, approve };
|
|
425
492
|
const transcript = [{ role: 'user', content: userMessage }];
|
|
426
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)}`;
|
|
427
516
|
|
|
428
517
|
for (let step = 0; step < MAX_STEPS; step++) {
|
|
429
518
|
if (signal.aborted) { yield { type: 'aborted' }; return; }
|
|
@@ -455,7 +544,7 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
|
|
|
455
544
|
}
|
|
456
545
|
messages.push({ role: 'assistant', content: reply });
|
|
457
546
|
|
|
458
|
-
const { prose, calls, recovered, malformed } = parseReply(reply);
|
|
547
|
+
const { prose, calls, recovered, malformed, truncated } = parseReply(reply);
|
|
459
548
|
if (prose) {
|
|
460
549
|
yield { type: 'text', text: prose };
|
|
461
550
|
transcript.push({ role: 'assistant', content: prose });
|
|
@@ -465,6 +554,11 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
|
|
|
465
554
|
// A reply that was clearly *reaching* for an action but came out
|
|
466
555
|
// unparseable must not be mistaken for "finished" — that silently drops
|
|
467
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
|
+
}
|
|
468
562
|
if (malformed && malformedRetries < MAX_MALFORMED_RETRIES) {
|
|
469
563
|
malformedRetries++;
|
|
470
564
|
messages.push({ role: 'user', content: FORMAT_CORRECTION });
|
|
@@ -499,6 +593,23 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
|
|
|
499
593
|
continue;
|
|
500
594
|
}
|
|
501
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
|
+
|
|
502
613
|
yield { type: 'tool_start', name: call.name, args: call.args };
|
|
503
614
|
|
|
504
615
|
const needsApproval = TOOL_NEEDS_APPROVAL.has(call.name);
|
|
@@ -519,6 +630,33 @@ export async function* runAgent({ userMessage, history, mode, cwd, approve, sign
|
|
|
519
630
|
|
|
520
631
|
messages.push({ role: 'user', content: `[tool result: ${call.name}]\n${out.output}` });
|
|
521
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
|
+
|
|
522
660
|
// A declined action is the user's answer, not a retry prompt.
|
|
523
661
|
if (out.meta?.rejected) {
|
|
524
662
|
messages.push({
|