codeply-cli 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/THIRD_PARTY_NOTICES.md +65 -63
- package/bin/codeply.js +1 -1
- package/lib/agent.mjs +133 -24
- package/lib/ai.js +268 -9
- package/lib/ai.mjs +6 -6
- package/lib/apply-patch.js +217 -217
- package/lib/arity-table.json +138 -138
- package/lib/arity.js +70 -70
- package/lib/auth.mjs +6 -6
- package/lib/bots.js +82 -17
- package/lib/chatgpt.js +282 -0
- package/lib/cloud-browser.mjs +143 -143
- package/lib/cloud.mjs +1186 -1144
- package/lib/commands.js +103 -103
- package/lib/config.js +83 -3
- package/lib/design-library/assemble-artifact.mjs +21 -21
- package/lib/design-library/cleanup.mjs +45 -45
- package/lib/design-library/index.json +22379 -22379
- package/lib/design-library/make-table-data.mjs +39 -39
- package/lib/diagnostics.js +192 -192
- package/lib/edit-engine.js +364 -364
- package/lib/github-agent.mjs +311 -311
- package/lib/history.js +75 -75
- package/lib/mail-watch.js +504 -0
- package/lib/mcp.js +258 -258
- package/lib/model-sync.js +114 -0
- package/lib/native-tools.mjs +16 -1
- package/lib/oauth-connectors.js +146 -24
- package/lib/permissions.js +109 -109
- package/lib/plugins.js +353 -353
- package/lib/publish.js +916 -0
- package/lib/reach.js +67 -0
- package/lib/server.mjs +457 -457
- package/lib/session-db.js +220 -220
- package/lib/share.js +168 -168
- package/lib/skills.js +2 -0
- package/lib/snapshot.js +146 -146
- package/lib/telemetry.js +102 -0
- package/lib/terminal-hints.js +154 -154
- package/lib/tools.mjs +316 -14
- package/lib/typecheck-worker.js +147 -147
- package/lib/web-tools.js +209 -158
- package/package.json +49 -49
- package/skills/SOURCE.md +58 -58
- package/skills/accessibility/SKILL.md +147 -147
- package/skills/agent-architecture-audit/SKILL.md +257 -257
- package/skills/agent-eval/SKILL.md +147 -147
- package/skills/agent-harness-construction/SKILL.md +74 -74
- package/skills/agent-introspection-debugging/SKILL.md +154 -154
- package/skills/agent-payment-x402/SKILL.md +225 -225
- package/skills/agent-self-evaluation/SKILL.md +182 -182
- package/skills/agent-self-evaluation/references/evaluation-criteria.md +71 -71
- package/skills/agent-self-evaluation/references/hook-integration.md +64 -64
- package/skills/agent-sort/SKILL.md +216 -216
- package/skills/agentic-engineering/SKILL.md +64 -64
- package/skills/agentic-os/SKILL.md +388 -388
- package/skills/ai-first-engineering/SKILL.md +52 -52
- package/skills/ai-regression-testing/SKILL.md +386 -386
- package/skills/android-clean-architecture/SKILL.md +340 -340
- package/skills/angular-developer/SKILL.md +155 -155
- package/skills/angular-developer/references/angular-animations.md +160 -160
- package/skills/angular-developer/references/angular-aria.md +410 -410
- package/skills/angular-developer/references/cli.md +86 -86
- package/skills/angular-developer/references/component-harnesses.md +59 -59
- package/skills/angular-developer/references/component-styling.md +91 -91
- package/skills/angular-developer/references/components.md +117 -117
- package/skills/angular-developer/references/creating-services.md +97 -97
- package/skills/angular-developer/references/data-resolvers.md +69 -69
- package/skills/angular-developer/references/define-routes.md +67 -67
- package/skills/angular-developer/references/defining-providers.md +72 -72
- package/skills/angular-developer/references/di-fundamentals.md +120 -120
- package/skills/angular-developer/references/e2e-testing.md +56 -56
- package/skills/angular-developer/references/effects.md +83 -83
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -43
- package/skills/angular-developer/references/host-elements.md +80 -80
- package/skills/angular-developer/references/injection-context.md +63 -63
- package/skills/angular-developer/references/inputs.md +101 -101
- package/skills/angular-developer/references/linked-signal.md +59 -59
- package/skills/angular-developer/references/loading-strategies.md +61 -61
- package/skills/angular-developer/references/mcp.md +108 -108
- package/skills/angular-developer/references/navigate-to-routes.md +69 -69
- package/skills/angular-developer/references/outputs.md +86 -86
- package/skills/angular-developer/references/reactive-forms.md +122 -122
- package/skills/angular-developer/references/rendering-strategies.md +44 -44
- package/skills/angular-developer/references/resource.md +77 -77
- package/skills/angular-developer/references/route-animations.md +56 -56
- package/skills/angular-developer/references/route-guards.md +52 -52
- package/skills/angular-developer/references/router-lifecycle.md +45 -45
- package/skills/angular-developer/references/router-testing.md +87 -87
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -68
- package/skills/angular-developer/references/signal-forms.md +795 -795
- package/skills/angular-developer/references/signals-overview.md +94 -94
- package/skills/angular-developer/references/tailwind-css.md +69 -69
- package/skills/angular-developer/references/template-driven-forms.md +114 -114
- package/skills/angular-developer/references/testing-fundamentals.md +65 -65
- package/skills/api-connector-builder/SKILL.md +121 -121
- package/skills/api-design/SKILL.md +524 -524
- package/skills/architecture-decision-records/SKILL.md +180 -180
- package/skills/article-writing/SKILL.md +80 -80
- package/skills/automation-audit-ops/SKILL.md +143 -143
- package/skills/autonomous-agent-harness/SKILL.md +274 -274
- package/skills/autonomous-loops/SKILL.md +611 -611
- package/skills/backend-patterns/SKILL.md +562 -562
- package/skills/benchmark/SKILL.md +95 -95
- package/skills/benchmark-methodology/SKILL.md +191 -191
- package/skills/benchmark-optimization-loop/SKILL.md +71 -71
- package/skills/blender-motion-state-inspection/SKILL.md +165 -165
- package/skills/blueprint/SKILL.md +106 -106
- package/skills/brand-discovery/SKILL.md +145 -145
- package/skills/brand-discovery/references/10_purpose-why.md +40 -40
- package/skills/brand-discovery/references/20_positioning.md +44 -44
- package/skills/brand-discovery/references/30_audience-niche.md +52 -52
- package/skills/brand-discovery/references/40_personality-archetype.md +57 -57
- package/skills/brand-discovery/references/50_voice-tone.md +59 -59
- package/skills/brand-discovery/references/60_narrative-story.md +50 -50
- package/skills/brand-discovery/references/70_founder-tension.md +49 -49
- package/skills/brand-discovery/references/90_SYNTHESIS.md +133 -133
- package/skills/brand-voice/SKILL.md +98 -98
- package/skills/brand-voice/references/voice-profile-schema.md +55 -55
- package/skills/browser-qa/SKILL.md +105 -105
- package/skills/bun-runtime/SKILL.md +85 -85
- package/skills/canary-watch/SKILL.md +108 -108
- package/skills/carrier-relationship-management/SKILL.md +212 -212
- package/skills/cisco-ios-patterns/SKILL.md +164 -164
- package/skills/ck/SKILL.md +148 -148
- package/skills/claude-devfleet/SKILL.md +112 -112
- package/skills/click-path-audit/SKILL.md +245 -245
- package/skills/clickhouse-io/SKILL.md +445 -445
- package/skills/code-tour/SKILL.md +254 -254
- package/skills/codebase-onboarding/SKILL.md +234 -234
- package/skills/codehealth-mcp/SKILL.md +167 -167
- package/skills/coding-standards/SKILL.md +551 -551
- package/skills/competitive-platform-analysis/SKILL.md +214 -214
- package/skills/competitive-report-structure/SKILL.md +162 -162
- package/skills/compose-multiplatform-patterns/SKILL.md +300 -300
- package/skills/config-gc/SKILL.md +120 -120
- package/skills/configure-ecc/SKILL.md +385 -385
- package/skills/connections-optimizer/SKILL.md +190 -190
- package/skills/content-engine/SKILL.md +132 -132
- package/skills/content-hash-cache-pattern/SKILL.md +162 -162
- package/skills/context-budget/SKILL.md +136 -136
- package/skills/continuous-agent-loop/SKILL.md +46 -46
- package/skills/continuous-learning/SKILL.md +132 -132
- package/skills/continuous-learning-v2/SKILL.md +361 -361
- package/skills/contract-first/SKILL.md +287 -287
- package/skills/cost-aware-llm-pipeline/SKILL.md +184 -184
- package/skills/cost-tracking/SKILL.md +97 -97
- package/skills/council/SKILL.md +204 -204
- package/skills/cpp-coding-standards/SKILL.md +724 -724
- package/skills/cpp-testing/SKILL.md +325 -325
- package/skills/crosspost/SKILL.md +112 -112
- package/skills/csharp-testing/SKILL.md +322 -322
- package/skills/customer-billing-ops/SKILL.md +141 -141
- package/skills/customs-trade-compliance/SKILL.md +263 -263
- package/skills/dart-flutter-patterns/SKILL.md +564 -564
- package/skills/dashboard-builder/SKILL.md +109 -109
- package/skills/data-scraper-agent/SKILL.md +765 -765
- package/skills/data-throughput-accelerator/SKILL.md +74 -74
- package/skills/database-migrations/SKILL.md +430 -430
- package/skills/deep-research/SKILL.md +160 -160
- package/skills/defi-amm-security/SKILL.md +167 -167
- package/skills/delivery-gate/SKILL.md +126 -126
- package/skills/deployment-patterns/SKILL.md +428 -428
- package/skills/design-system/SKILL.md +83 -83
- package/skills/django-celery/SKILL.md +458 -458
- package/skills/django-patterns/SKILL.md +735 -735
- package/skills/django-security/SKILL.md +644 -644
- package/skills/django-tdd/SKILL.md +730 -730
- package/skills/django-verification/SKILL.md +470 -470
- package/skills/dmux-workflows/SKILL.md +192 -192
- package/skills/docker-patterns/SKILL.md +365 -365
- package/skills/documentation-lookup/SKILL.md +91 -91
- package/skills/dotnet-patterns/SKILL.md +322 -322
- package/skills/dynamic-workflow-mode/SKILL.md +124 -124
- package/skills/e2e-testing/SKILL.md +327 -327
- package/skills/ecc-guide/SKILL.md +190 -190
- package/skills/ecc-recipes/SKILL.md +149 -149
- package/skills/ecc-tools-cost-audit/SKILL.md +161 -161
- package/skills/email-ops/SKILL.md +122 -122
- package/skills/energy-procurement/SKILL.md +228 -228
- package/skills/enterprise-agent-ops/SKILL.md +51 -51
- package/skills/error-handling/SKILL.md +377 -377
- package/skills/eval-harness/SKILL.md +271 -271
- package/skills/evm-token-decimals/SKILL.md +131 -131
- package/skills/exa-search/SKILL.md +108 -108
- package/skills/fal-ai-media/SKILL.md +289 -289
- package/skills/fastapi-patterns/SKILL.md +514 -514
- package/skills/finance-billing-ops/SKILL.md +128 -128
- package/skills/flox-environments/SKILL.md +497 -497
- package/skills/flutter-dart-code-review/SKILL.md +436 -436
- package/skills/foundation-models-on-device/SKILL.md +243 -243
- package/skills/frontend-a11y/SKILL.md +446 -446
- package/skills/frontend-design-direction/SKILL.md +93 -93
- package/skills/frontend-patterns/SKILL.md +657 -657
- package/skills/frontend-slides/SKILL.md +185 -185
- package/skills/fsharp-testing/SKILL.md +281 -281
- package/skills/gan-style-harness/SKILL.md +279 -279
- package/skills/gateguard/SKILL.md +133 -133
- package/skills/generating-python-installer/SKILL.md +820 -820
- package/skills/git-workflow/SKILL.md +716 -716
- package/skills/github-ops/SKILL.md +145 -145
- package/skills/golang-patterns/SKILL.md +676 -676
- package/skills/golang-testing/SKILL.md +721 -721
- package/skills/google-workspace-ops/SKILL.md +96 -96
- package/skills/growth-log/SKILL.md +128 -128
- package/skills/healthcare-cdss-patterns/SKILL.md +246 -246
- package/skills/healthcare-emr-patterns/SKILL.md +160 -160
- package/skills/healthcare-eval-harness/SKILL.md +208 -208
- package/skills/healthcare-phi-compliance/SKILL.md +146 -146
- package/skills/hermes-imports/SKILL.md +89 -89
- package/skills/hexagonal-architecture/SKILL.md +277 -277
- package/skills/hipaa-compliance/SKILL.md +79 -79
- package/skills/homelab-network-readiness/SKILL.md +170 -170
- package/skills/homelab-network-setup/SKILL.md +130 -130
- package/skills/homelab-pihole-dns/SKILL.md +275 -275
- package/skills/homelab-vlan-segmentation/SKILL.md +312 -312
- package/skills/homelab-wireguard-vpn/SKILL.md +306 -306
- package/skills/hookify-rules/SKILL.md +128 -128
- package/skills/inherit-legacy-style/SKILL.md +157 -157
- package/skills/intent-driven-development/SKILL.md +360 -360
- package/skills/inventory-demand-planning/SKILL.md +247 -247
- package/skills/investor-materials/SKILL.md +97 -97
- package/skills/investor-outreach/SKILL.md +92 -92
- package/skills/ios-icon-gen/SKILL.md +158 -158
- package/skills/iterative-retrieval/SKILL.md +212 -212
- package/skills/ito-basket-compare/SKILL.md +64 -64
- package/skills/ito-compute/SKILL.md +129 -129
- package/skills/ito-data-atlas-agent/SKILL.md +64 -64
- package/skills/ito-market-intelligence/SKILL.md +61 -61
- package/skills/ito-trade-planner/SKILL.md +68 -68
- package/skills/java-coding-standards/SKILL.md +384 -384
- package/skills/jira-integration/SKILL.md +303 -303
- package/skills/jpa-patterns/SKILL.md +152 -152
- package/skills/knowledge-ops/SKILL.md +155 -155
- package/skills/kotlin-coroutines-flows/SKILL.md +285 -285
- package/skills/kotlin-exposed-patterns/SKILL.md +720 -720
- package/skills/kotlin-ktor-patterns/SKILL.md +690 -690
- package/skills/kotlin-patterns/SKILL.md +712 -712
- package/skills/kotlin-testing/SKILL.md +825 -825
- package/skills/kubernetes-patterns/SKILL.md +756 -756
- package/skills/laravel-patterns/SKILL.md +416 -416
- package/skills/laravel-plugin-discovery/SKILL.md +230 -230
- package/skills/laravel-security/SKILL.md +948 -948
- package/skills/laravel-tdd/SKILL.md +675 -675
- package/skills/laravel-verification/SKILL.md +180 -180
- package/skills/latency-critical-systems/SKILL.md +75 -75
- package/skills/lead-intelligence/SKILL.md +322 -322
- package/skills/liquid-glass-design/SKILL.md +279 -279
- package/skills/llm-trading-agent-security/SKILL.md +147 -147
- package/skills/logistics-exception-management/SKILL.md +222 -222
- package/skills/loop-design-check/SKILL.md +143 -143
- package/skills/mailtrap-email-integration/SKILL.md +77 -77
- package/skills/make-interfaces-feel-better/SKILL.md +152 -152
- package/skills/manim-video/SKILL.md +90 -90
- package/skills/market-research/SKILL.md +76 -76
- package/skills/marketing-campaign/SKILL.md +114 -114
- package/skills/mcp-server-patterns/SKILL.md +70 -70
- package/skills/messages-ops/SKILL.md +105 -105
- package/skills/ml-adoption-playbook/SKILL.md +57 -57
- package/skills/mle-workflow/SKILL.md +348 -348
- package/skills/motion-advanced/SKILL.md +596 -596
- package/skills/motion-foundations/SKILL.md +299 -299
- package/skills/motion-patterns/SKILL.md +434 -434
- package/skills/motion-ui/SKILL.md +576 -576
- package/skills/mysql-patterns/SKILL.md +413 -413
- package/skills/nanoclaw-repl/SKILL.md +34 -34
- package/skills/nestjs-patterns/SKILL.md +231 -231
- package/skills/netmiko-ssh-automation/SKILL.md +174 -174
- package/skills/network-bgp-diagnostics/SKILL.md +168 -168
- package/skills/network-config-validation/SKILL.md +211 -211
- package/skills/network-interface-health/SKILL.md +153 -153
- package/skills/nextjs-turbopack/SKILL.md +58 -58
- package/skills/nodejs-keccak256/SKILL.md +103 -103
- package/skills/nutrient-document-processing/SKILL.md +168 -168
- package/skills/nuxt4-patterns/SKILL.md +101 -101
- package/skills/openclaw-persona-forge/SKILL.md +289 -289
- package/skills/openclaw-persona-forge/references/avatar-style.md +124 -124
- package/skills/openclaw-persona-forge/references/boundary-rules.md +53 -53
- package/skills/openclaw-persona-forge/references/error-handling.md +53 -53
- package/skills/openclaw-persona-forge/references/identity-tension.md +48 -48
- package/skills/openclaw-persona-forge/references/naming-system.md +39 -39
- package/skills/openclaw-persona-forge/references/output-template.md +166 -166
- package/skills/opensource-pipeline/SKILL.md +256 -256
- package/skills/orch-add-feature/SKILL.md +45 -45
- package/skills/orch-build-mvp/SKILL.md +49 -49
- package/skills/orch-change-feature/SKILL.md +43 -43
- package/skills/orch-fix-defect/SKILL.md +43 -43
- package/skills/orch-pipeline/SKILL.md +121 -121
- package/skills/orch-refine-code/SKILL.md +44 -44
- package/skills/parallel-execution-optimizer/SKILL.md +74 -74
- package/skills/perl-patterns/SKILL.md +505 -505
- package/skills/perl-security/SKILL.md +504 -504
- package/skills/perl-testing/SKILL.md +476 -476
- package/skills/plan-canvas/SKILL.md +153 -153
- package/skills/plan-orchestrate/SKILL.md +263 -263
- package/skills/plankton-code-quality/SKILL.md +237 -237
- package/skills/postgres-patterns/SKILL.md +148 -148
- package/skills/prediction-market-oracle-research/SKILL.md +64 -64
- package/skills/prediction-market-risk-review/SKILL.md +61 -61
- package/skills/premium-web-design/SKILL.md +154 -154
- package/skills/prisma-patterns/SKILL.md +401 -401
- package/skills/product-capability/SKILL.md +142 -142
- package/skills/product-lens/SKILL.md +93 -93
- package/skills/production-audit/SKILL.md +207 -207
- package/skills/production-scheduling/SKILL.md +238 -238
- package/skills/project-flow-ops/SKILL.md +112 -112
- package/skills/prompt-optimizer/SKILL.md +398 -398
- package/skills/publish-website/SKILL.md +76 -0
- package/skills/python-patterns/SKILL.md +751 -751
- package/skills/python-testing/SKILL.md +817 -817
- package/skills/pytorch-patterns/SKILL.md +397 -397
- package/skills/quality-nonconformance/SKILL.md +260 -260
- package/skills/quarkus-patterns/SKILL.md +723 -723
- package/skills/quarkus-security/SKILL.md +468 -468
- package/skills/quarkus-tdd/SKILL.md +812 -812
- package/skills/quarkus-verification/SKILL.md +480 -480
- package/skills/ralphinho-rfc-pipeline/SKILL.md +68 -68
- package/skills/react-native-patterns/SKILL.md +326 -326
- package/skills/react-patterns/SKILL.md +342 -342
- package/skills/react-performance/SKILL.md +575 -575
- package/skills/react-testing/SKILL.md +424 -424
- package/skills/recsys-pipeline-architect/SKILL.md +115 -115
- package/skills/recursive-decision-ledger/SKILL.md +81 -81
- package/skills/redis-patterns/SKILL.md +404 -404
- package/skills/regex-vs-llm-structured-text/SKILL.md +221 -221
- package/skills/remotion-video-creation/SKILL.md +43 -43
- package/skills/repo-scan/SKILL.md +79 -79
- package/skills/research-ops/SKILL.md +113 -113
- package/skills/returns-reverse-logistics/SKILL.md +240 -240
- package/skills/rules-distill/SKILL.md +265 -265
- package/skills/rust-patterns/SKILL.md +500 -500
- package/skills/rust-testing/SKILL.md +501 -501
- package/skills/safety-guard/SKILL.md +76 -76
- package/skills/santa-method/SKILL.md +307 -307
- package/skills/scientific-db-pubmed-database/SKILL.md +176 -176
- package/skills/scientific-db-uspto-database/SKILL.md +178 -178
- package/skills/scientific-pkg-gget/SKILL.md +167 -167
- package/skills/scientific-thinking-literature-review/SKILL.md +193 -193
- package/skills/scientific-thinking-scholar-evaluation/SKILL.md +161 -161
- package/skills/search-first/SKILL.md +183 -183
- package/skills/security-bounty-hunter/SKILL.md +100 -100
- package/skills/security-review/SKILL.md +504 -504
- package/skills/security-scan/SKILL.md +166 -166
- package/skills/seo/SKILL.md +155 -155
- package/skills/skill-comply/SKILL.md +59 -59
- package/skills/skill-scout/SKILL.md +141 -141
- package/skills/skill-stocktake/SKILL.md +195 -195
- package/skills/social-graph-ranker/SKILL.md +155 -155
- package/skills/social-publisher/SKILL.md +130 -130
- package/skills/springboot-patterns/SKILL.md +315 -315
- package/skills/springboot-security/SKILL.md +273 -273
- package/skills/springboot-tdd/SKILL.md +159 -159
- package/skills/springboot-verification/SKILL.md +232 -232
- package/skills/strategic-compact/SKILL.md +142 -142
- package/skills/swift-actor-persistence/SKILL.md +144 -144
- package/skills/swift-concurrency-6-2/SKILL.md +216 -216
- package/skills/swift-protocol-di-testing/SKILL.md +191 -191
- package/skills/swiftui-patterns/SKILL.md +259 -259
- package/skills/taste/SKILL.md +264 -264
- package/skills/taste/references/genre-taxonomy.md +87 -87
- package/skills/tdd-workflow/SKILL.md +583 -583
- package/skills/team-agent-orchestration/SKILL.md +111 -111
- package/skills/team-builder/SKILL.md +169 -169
- package/skills/terminal-ops/SKILL.md +110 -110
- package/skills/tinystruct-patterns/SKILL.md +279 -279
- package/skills/tinystruct-patterns/references/architecture.md +90 -90
- package/skills/tinystruct-patterns/references/data-handling.md +60 -60
- package/skills/tinystruct-patterns/references/database.md +99 -99
- package/skills/tinystruct-patterns/references/routing.md +64 -64
- package/skills/tinystruct-patterns/references/system-usage.md +97 -97
- package/skills/tinystruct-patterns/references/testing.md +72 -72
- package/skills/token-budget-advisor/SKILL.md +134 -134
- package/skills/ui-demo/SKILL.md +466 -466
- package/skills/ui-to-vue/SKILL.md +135 -135
- package/skills/uncloud/SKILL.md +344 -344
- package/skills/unified-memory/SKILL.md +170 -170
- package/skills/unified-notifications-ops/SKILL.md +188 -188
- package/skills/verification-loop/SKILL.md +129 -129
- package/skills/video-editing/SKILL.md +311 -311
- package/skills/videodb/SKILL.md +375 -375
- package/skills/visa-doc-translate/SKILL.md +117 -117
- package/skills/vite-patterns/SKILL.md +450 -450
- package/skills/vue-patterns/SKILL.md +471 -471
- package/skills/windows-desktop-e2e/SKILL.md +888 -888
- package/skills/workspace-surface-audit/SKILL.md +126 -126
- package/skills/x-api/SKILL.md +235 -235
package/lib/apply-patch.js
CHANGED
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The "apply_patch" format: several file changes (add, update, delete, move) in
|
|
3
|
-
* one block, as opencode's and Codex's apply_patch tools accept.
|
|
4
|
-
*
|
|
5
|
-
* *** Begin Patch
|
|
6
|
-
* *** Add File: src/new.js
|
|
7
|
-
* +line one
|
|
8
|
-
* *** Update File: src/app.js
|
|
9
|
-
* *** Move to: src/main.js (optional)
|
|
10
|
-
* @@ function start() (optional anchor line)
|
|
11
|
-
* unchanged context
|
|
12
|
-
* -old line
|
|
13
|
-
* +new line
|
|
14
|
-
* *** Delete File: old.txt
|
|
15
|
-
* *** End Patch
|
|
16
|
-
*
|
|
17
|
-
* parsePatch() and planPatch() are pure (no disk writes) so a caller can show
|
|
18
|
-
* the user the whole change, ask, and only then write it.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
function normalize(s) {
|
|
22
|
-
return s.replace(/[‐-―−]/g, '-').replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/ /g, ' ');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** @returns {{hunks: object[]}|{error: string}} */
|
|
26
|
-
function parsePatch(text) {
|
|
27
|
-
let lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
28
|
-
// Models sometimes wrap the patch in a heredoc or a code fence.
|
|
29
|
-
while (lines.length && !lines[0].trim()) lines.shift();
|
|
30
|
-
if (lines.length && /^(?:apply_patch\s*)?<<-?\s*['"]?\w+['"]?\s*$/.test(lines[0].trim())) lines.shift();
|
|
31
|
-
if (lines.length && /^```/.test(lines[0].trim())) lines.shift();
|
|
32
|
-
const begin = lines.findIndex((l) => l.trim() === '*** Begin Patch');
|
|
33
|
-
if (begin === -1) return { error: 'The patch must start with "*** Begin Patch" on its own line.' };
|
|
34
|
-
lines = lines.slice(begin + 1);
|
|
35
|
-
let end = lines.findIndex((l) => l.trim() === '*** End Patch');
|
|
36
|
-
if (end === -1) end = lines.length; // a missing footer is forgivable, the hunks are still complete lines
|
|
37
|
-
lines = lines.slice(0, end);
|
|
38
|
-
|
|
39
|
-
const hunks = [];
|
|
40
|
-
let i = 0;
|
|
41
|
-
const isHeader = (l) => /^\*\*\* (Add File|Update File|Delete File|Move to|End of File):?/.test(l);
|
|
42
|
-
while (i < lines.length) {
|
|
43
|
-
const line = lines[i];
|
|
44
|
-
if (!line.trim()) { i++; continue; }
|
|
45
|
-
let m;
|
|
46
|
-
if ((m = /^\*\*\* Add File:\s*(.+)$/.exec(line))) {
|
|
47
|
-
const body = [];
|
|
48
|
-
i++;
|
|
49
|
-
while (i < lines.length && !isHeader(lines[i]) ) {
|
|
50
|
-
if (lines[i].startsWith('+')) body.push(lines[i].slice(1));
|
|
51
|
-
else if (lines[i] === '') body.push('');
|
|
52
|
-
else return { error: `In "Add File: ${m[1].trim()}", every line must start with "+" (got: ${JSON.stringify(lines[i].slice(0, 60))}).` };
|
|
53
|
-
i++;
|
|
54
|
-
}
|
|
55
|
-
while (body.length > 1 && body[body.length - 1] === '') body.pop();
|
|
56
|
-
hunks.push({ type: 'add', path: m[1].trim(), contents: body.join('\n') + (body.length ? '\n' : '') });
|
|
57
|
-
} else if ((m = /^\*\*\* Delete File:\s*(.+)$/.exec(line))) {
|
|
58
|
-
hunks.push({ type: 'delete', path: m[1].trim() });
|
|
59
|
-
i++;
|
|
60
|
-
} else if ((m = /^\*\*\* Update File:\s*(.+)$/.exec(line))) {
|
|
61
|
-
const hunk = { type: 'update', path: m[1].trim(), movePath: null, chunks: [] };
|
|
62
|
-
i++;
|
|
63
|
-
if (i < lines.length && (m = /^\*\*\* Move to:\s*(.+)$/.exec(lines[i]))) { hunk.movePath = m[1].trim(); i++; }
|
|
64
|
-
let cur = null;
|
|
65
|
-
const flush = () => { if (cur && (cur.old.length || cur.new.length)) hunk.chunks.push(cur); cur = null; };
|
|
66
|
-
while (i < lines.length && !/^\*\*\* (Add File|Update File|Delete File)/.test(lines[i])) {
|
|
67
|
-
const l = lines[i];
|
|
68
|
-
if (l.startsWith('@@')) {
|
|
69
|
-
flush();
|
|
70
|
-
cur = { ctx: l.slice(2).trim(), old: [], new: [], eof: false };
|
|
71
|
-
} else if (l.trim() === '*** End of File') {
|
|
72
|
-
if (cur) cur.eof = true;
|
|
73
|
-
} else {
|
|
74
|
-
if (!cur) cur = { ctx: '', old: [], new: [], eof: false };
|
|
75
|
-
if (l.startsWith('+')) cur.new.push(l.slice(1));
|
|
76
|
-
else if (l.startsWith('-')) cur.old.push(l.slice(1));
|
|
77
|
-
else if (l.startsWith(' ')) { cur.old.push(l.slice(1)); cur.new.push(l.slice(1)); }
|
|
78
|
-
else if (l === '') { cur.old.push(''); cur.new.push(''); }
|
|
79
|
-
else return { error: `In "Update File: ${hunk.path}", a change line must start with " ", "-" or "+" (got: ${JSON.stringify(l.slice(0, 60))}).` };
|
|
80
|
-
}
|
|
81
|
-
i++;
|
|
82
|
-
}
|
|
83
|
-
flush();
|
|
84
|
-
if (!hunk.chunks.length && !hunk.movePath) return { error: `"Update File: ${hunk.path}" has no changes.` };
|
|
85
|
-
hunks.push(hunk);
|
|
86
|
-
} else {
|
|
87
|
-
return { error: `Unexpected line in the patch: ${JSON.stringify(line.slice(0, 80))}. Each section starts with "*** Add File:", "*** Update File:" or "*** Delete File:".` };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (!hunks.length) return { error: 'The patch has no changes.' };
|
|
91
|
-
return { hunks };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Find `needle` (array of lines) in `hay` at or after `from`, loosening whitespace step by step. */
|
|
95
|
-
function seek(hay, needle, from, eof) {
|
|
96
|
-
if (!needle.length) return from;
|
|
97
|
-
const passes = [
|
|
98
|
-
(s) => s,
|
|
99
|
-
(s) => s.replace(/\s+$/, ''),
|
|
100
|
-
(s) => s.trim(),
|
|
101
|
-
(s) => normalize(s).trim(),
|
|
102
|
-
];
|
|
103
|
-
for (const f of passes) {
|
|
104
|
-
const start = eof ? Math.max(from, hay.length - needle.length) : from;
|
|
105
|
-
for (let a = start; a + needle.length <= hay.length; a++) {
|
|
106
|
-
let ok = true;
|
|
107
|
-
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
108
|
-
if (ok) return a;
|
|
109
|
-
}
|
|
110
|
-
if (eof) {
|
|
111
|
-
// The end-of-file marker is a hint, not a promise: fall back to searching everywhere.
|
|
112
|
-
for (let a = from; a + needle.length <= hay.length; a++) {
|
|
113
|
-
let ok = true;
|
|
114
|
-
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
115
|
-
if (ok) return a;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return -1;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function applyChunks(original, chunks, label) {
|
|
123
|
-
const crlf = /\r\n/.test(original);
|
|
124
|
-
const text = original.replace(/\r\n/g, '\n');
|
|
125
|
-
const hay = text.split('\n');
|
|
126
|
-
const trailingNewline = hay.length > 1 && hay[hay.length - 1] === '';
|
|
127
|
-
if (trailingNewline) hay.pop();
|
|
128
|
-
|
|
129
|
-
const replacements = [];
|
|
130
|
-
let cursor = 0;
|
|
131
|
-
for (const c of chunks) {
|
|
132
|
-
if (c.ctx) {
|
|
133
|
-
const at = seek(hay, [c.ctx], cursor, false);
|
|
134
|
-
if (at === -1) return { error: `${label}: could not find the anchor line "${c.ctx}". Copy it exactly from the file.` };
|
|
135
|
-
cursor = at + 1;
|
|
136
|
-
}
|
|
137
|
-
if (!c.old.length) {
|
|
138
|
-
// Pure insertion: at the end of the file, unless an anchor put us elsewhere.
|
|
139
|
-
const at = c.ctx ? cursor : hay.length;
|
|
140
|
-
replacements.push([at, 0, c.new]);
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
let old = c.old;
|
|
144
|
-
let neu = c.new;
|
|
145
|
-
let at = seek(hay, old, cursor, c.eof);
|
|
146
|
-
// A trailing blank line in the chunk often stands for the end of the file.
|
|
147
|
-
if (at === -1 && old[old.length - 1] === '') {
|
|
148
|
-
old = old.slice(0, -1);
|
|
149
|
-
neu = neu[neu.length - 1] === '' ? neu.slice(0, -1) : neu;
|
|
150
|
-
at = seek(hay, old, cursor, c.eof);
|
|
151
|
-
}
|
|
152
|
-
if (at === -1) {
|
|
153
|
-
return { error: `${label}: could not find these lines to replace:\n${c.old.slice(0, 6).map((l) => ` ${l}`).join('\n')}\nRead the file again and copy the lines exactly.` };
|
|
154
|
-
}
|
|
155
|
-
replacements.push([at, old.length, neu]);
|
|
156
|
-
cursor = at + old.length;
|
|
157
|
-
}
|
|
158
|
-
replacements.sort((a, b) => a[0] - b[0]);
|
|
159
|
-
const out = hay.slice();
|
|
160
|
-
for (let k = replacements.length - 1; k >= 0; k--) {
|
|
161
|
-
const [at, len, neu] = replacements[k];
|
|
162
|
-
out.splice(at, len, ...neu);
|
|
163
|
-
}
|
|
164
|
-
let result = out.join('\n') + (trailingNewline || !original ? '\n' : '');
|
|
165
|
-
if (crlf) result = result.replace(/\n/g, '\r\n');
|
|
166
|
-
return { content: result };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Work out what a patch would do, without touching the disk.
|
|
171
|
-
* @param {string} patchText
|
|
172
|
-
* @param {(path:string)=>string|null} readFile current content of a project path, null if it doesn't exist
|
|
173
|
-
* @returns {{changes: Array<{kind:'add'|'update'|'delete'|'move', path:string, newPath?:string, before:string, after:string, added:number, removed:number}>}|{error:string}}
|
|
174
|
-
*/
|
|
175
|
-
function planPatch(patchText, readFile) {
|
|
176
|
-
const parsed = parsePatch(patchText);
|
|
177
|
-
if (parsed.error) return { error: parsed.error };
|
|
178
|
-
const changes = [];
|
|
179
|
-
const virtual = new Map(); // paths changed earlier in this same patch
|
|
180
|
-
const current = (p) => (virtual.has(p) ? virtual.get(p) : readFile(p));
|
|
181
|
-
for (const h of parsed.hunks) {
|
|
182
|
-
if (h.type === 'add') {
|
|
183
|
-
const existing = current(h.path);
|
|
184
|
-
if (existing != null && existing !== '') return { error: `Cannot add ${h.path}: it already exists. Use "*** Update File:" to change it.` };
|
|
185
|
-
changes.push({ kind: 'add', path: h.path, before: '', after: h.contents, added: h.contents.split('\n').length - 1, removed: 0 });
|
|
186
|
-
virtual.set(h.path, h.contents);
|
|
187
|
-
} else if (h.type === 'delete') {
|
|
188
|
-
const existing = current(h.path);
|
|
189
|
-
if (existing == null) return { error: `Cannot delete ${h.path}: no such file.` };
|
|
190
|
-
changes.push({ kind: 'delete', path: h.path, before: existing, after: '', added: 0, removed: existing.split('\n').length });
|
|
191
|
-
virtual.set(h.path, null);
|
|
192
|
-
} else {
|
|
193
|
-
const existing = current(h.path);
|
|
194
|
-
if (existing == null) return { error: `Cannot update ${h.path}: no such file.` };
|
|
195
|
-
let after = existing;
|
|
196
|
-
if (h.chunks.length) {
|
|
197
|
-
const r = applyChunks(existing, h.chunks, h.path);
|
|
198
|
-
if (r.error) return { error: r.error };
|
|
199
|
-
after = r.content;
|
|
200
|
-
}
|
|
201
|
-
const added = h.chunks.reduce((n, c) => n + c.new.length, 0);
|
|
202
|
-
const removed = h.chunks.reduce((n, c) => n + c.old.length, 0);
|
|
203
|
-
if (h.movePath) {
|
|
204
|
-
if (current(h.movePath) != null) return { error: `Cannot move ${h.path} to ${h.movePath}: that file already exists.` };
|
|
205
|
-
changes.push({ kind: 'move', path: h.path, newPath: h.movePath, before: existing, after, added, removed });
|
|
206
|
-
virtual.set(h.path, null);
|
|
207
|
-
virtual.set(h.movePath, after);
|
|
208
|
-
} else {
|
|
209
|
-
changes.push({ kind: 'update', path: h.path, before: existing, after, added, removed });
|
|
210
|
-
virtual.set(h.path, after);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return { changes };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
module.exports = { parsePatch, planPatch };
|
|
1
|
+
/**
|
|
2
|
+
* The "apply_patch" format: several file changes (add, update, delete, move) in
|
|
3
|
+
* one block, as opencode's and Codex's apply_patch tools accept.
|
|
4
|
+
*
|
|
5
|
+
* *** Begin Patch
|
|
6
|
+
* *** Add File: src/new.js
|
|
7
|
+
* +line one
|
|
8
|
+
* *** Update File: src/app.js
|
|
9
|
+
* *** Move to: src/main.js (optional)
|
|
10
|
+
* @@ function start() (optional anchor line)
|
|
11
|
+
* unchanged context
|
|
12
|
+
* -old line
|
|
13
|
+
* +new line
|
|
14
|
+
* *** Delete File: old.txt
|
|
15
|
+
* *** End Patch
|
|
16
|
+
*
|
|
17
|
+
* parsePatch() and planPatch() are pure (no disk writes) so a caller can show
|
|
18
|
+
* the user the whole change, ask, and only then write it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
function normalize(s) {
|
|
22
|
+
return s.replace(/[‐-―−]/g, '-').replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/ /g, ' ');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** @returns {{hunks: object[]}|{error: string}} */
|
|
26
|
+
function parsePatch(text) {
|
|
27
|
+
let lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
28
|
+
// Models sometimes wrap the patch in a heredoc or a code fence.
|
|
29
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
30
|
+
if (lines.length && /^(?:apply_patch\s*)?<<-?\s*['"]?\w+['"]?\s*$/.test(lines[0].trim())) lines.shift();
|
|
31
|
+
if (lines.length && /^```/.test(lines[0].trim())) lines.shift();
|
|
32
|
+
const begin = lines.findIndex((l) => l.trim() === '*** Begin Patch');
|
|
33
|
+
if (begin === -1) return { error: 'The patch must start with "*** Begin Patch" on its own line.' };
|
|
34
|
+
lines = lines.slice(begin + 1);
|
|
35
|
+
let end = lines.findIndex((l) => l.trim() === '*** End Patch');
|
|
36
|
+
if (end === -1) end = lines.length; // a missing footer is forgivable, the hunks are still complete lines
|
|
37
|
+
lines = lines.slice(0, end);
|
|
38
|
+
|
|
39
|
+
const hunks = [];
|
|
40
|
+
let i = 0;
|
|
41
|
+
const isHeader = (l) => /^\*\*\* (Add File|Update File|Delete File|Move to|End of File):?/.test(l);
|
|
42
|
+
while (i < lines.length) {
|
|
43
|
+
const line = lines[i];
|
|
44
|
+
if (!line.trim()) { i++; continue; }
|
|
45
|
+
let m;
|
|
46
|
+
if ((m = /^\*\*\* Add File:\s*(.+)$/.exec(line))) {
|
|
47
|
+
const body = [];
|
|
48
|
+
i++;
|
|
49
|
+
while (i < lines.length && !isHeader(lines[i]) ) {
|
|
50
|
+
if (lines[i].startsWith('+')) body.push(lines[i].slice(1));
|
|
51
|
+
else if (lines[i] === '') body.push('');
|
|
52
|
+
else return { error: `In "Add File: ${m[1].trim()}", every line must start with "+" (got: ${JSON.stringify(lines[i].slice(0, 60))}).` };
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
while (body.length > 1 && body[body.length - 1] === '') body.pop();
|
|
56
|
+
hunks.push({ type: 'add', path: m[1].trim(), contents: body.join('\n') + (body.length ? '\n' : '') });
|
|
57
|
+
} else if ((m = /^\*\*\* Delete File:\s*(.+)$/.exec(line))) {
|
|
58
|
+
hunks.push({ type: 'delete', path: m[1].trim() });
|
|
59
|
+
i++;
|
|
60
|
+
} else if ((m = /^\*\*\* Update File:\s*(.+)$/.exec(line))) {
|
|
61
|
+
const hunk = { type: 'update', path: m[1].trim(), movePath: null, chunks: [] };
|
|
62
|
+
i++;
|
|
63
|
+
if (i < lines.length && (m = /^\*\*\* Move to:\s*(.+)$/.exec(lines[i]))) { hunk.movePath = m[1].trim(); i++; }
|
|
64
|
+
let cur = null;
|
|
65
|
+
const flush = () => { if (cur && (cur.old.length || cur.new.length)) hunk.chunks.push(cur); cur = null; };
|
|
66
|
+
while (i < lines.length && !/^\*\*\* (Add File|Update File|Delete File)/.test(lines[i])) {
|
|
67
|
+
const l = lines[i];
|
|
68
|
+
if (l.startsWith('@@')) {
|
|
69
|
+
flush();
|
|
70
|
+
cur = { ctx: l.slice(2).trim(), old: [], new: [], eof: false };
|
|
71
|
+
} else if (l.trim() === '*** End of File') {
|
|
72
|
+
if (cur) cur.eof = true;
|
|
73
|
+
} else {
|
|
74
|
+
if (!cur) cur = { ctx: '', old: [], new: [], eof: false };
|
|
75
|
+
if (l.startsWith('+')) cur.new.push(l.slice(1));
|
|
76
|
+
else if (l.startsWith('-')) cur.old.push(l.slice(1));
|
|
77
|
+
else if (l.startsWith(' ')) { cur.old.push(l.slice(1)); cur.new.push(l.slice(1)); }
|
|
78
|
+
else if (l === '') { cur.old.push(''); cur.new.push(''); }
|
|
79
|
+
else return { error: `In "Update File: ${hunk.path}", a change line must start with " ", "-" or "+" (got: ${JSON.stringify(l.slice(0, 60))}).` };
|
|
80
|
+
}
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
flush();
|
|
84
|
+
if (!hunk.chunks.length && !hunk.movePath) return { error: `"Update File: ${hunk.path}" has no changes.` };
|
|
85
|
+
hunks.push(hunk);
|
|
86
|
+
} else {
|
|
87
|
+
return { error: `Unexpected line in the patch: ${JSON.stringify(line.slice(0, 80))}. Each section starts with "*** Add File:", "*** Update File:" or "*** Delete File:".` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (!hunks.length) return { error: 'The patch has no changes.' };
|
|
91
|
+
return { hunks };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Find `needle` (array of lines) in `hay` at or after `from`, loosening whitespace step by step. */
|
|
95
|
+
function seek(hay, needle, from, eof) {
|
|
96
|
+
if (!needle.length) return from;
|
|
97
|
+
const passes = [
|
|
98
|
+
(s) => s,
|
|
99
|
+
(s) => s.replace(/\s+$/, ''),
|
|
100
|
+
(s) => s.trim(),
|
|
101
|
+
(s) => normalize(s).trim(),
|
|
102
|
+
];
|
|
103
|
+
for (const f of passes) {
|
|
104
|
+
const start = eof ? Math.max(from, hay.length - needle.length) : from;
|
|
105
|
+
for (let a = start; a + needle.length <= hay.length; a++) {
|
|
106
|
+
let ok = true;
|
|
107
|
+
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
108
|
+
if (ok) return a;
|
|
109
|
+
}
|
|
110
|
+
if (eof) {
|
|
111
|
+
// The end-of-file marker is a hint, not a promise: fall back to searching everywhere.
|
|
112
|
+
for (let a = from; a + needle.length <= hay.length; a++) {
|
|
113
|
+
let ok = true;
|
|
114
|
+
for (let b = 0; b < needle.length; b++) if (f(hay[a + b]) !== f(needle[b])) { ok = false; break; }
|
|
115
|
+
if (ok) return a;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function applyChunks(original, chunks, label) {
|
|
123
|
+
const crlf = /\r\n/.test(original);
|
|
124
|
+
const text = original.replace(/\r\n/g, '\n');
|
|
125
|
+
const hay = text.split('\n');
|
|
126
|
+
const trailingNewline = hay.length > 1 && hay[hay.length - 1] === '';
|
|
127
|
+
if (trailingNewline) hay.pop();
|
|
128
|
+
|
|
129
|
+
const replacements = [];
|
|
130
|
+
let cursor = 0;
|
|
131
|
+
for (const c of chunks) {
|
|
132
|
+
if (c.ctx) {
|
|
133
|
+
const at = seek(hay, [c.ctx], cursor, false);
|
|
134
|
+
if (at === -1) return { error: `${label}: could not find the anchor line "${c.ctx}". Copy it exactly from the file.` };
|
|
135
|
+
cursor = at + 1;
|
|
136
|
+
}
|
|
137
|
+
if (!c.old.length) {
|
|
138
|
+
// Pure insertion: at the end of the file, unless an anchor put us elsewhere.
|
|
139
|
+
const at = c.ctx ? cursor : hay.length;
|
|
140
|
+
replacements.push([at, 0, c.new]);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
let old = c.old;
|
|
144
|
+
let neu = c.new;
|
|
145
|
+
let at = seek(hay, old, cursor, c.eof);
|
|
146
|
+
// A trailing blank line in the chunk often stands for the end of the file.
|
|
147
|
+
if (at === -1 && old[old.length - 1] === '') {
|
|
148
|
+
old = old.slice(0, -1);
|
|
149
|
+
neu = neu[neu.length - 1] === '' ? neu.slice(0, -1) : neu;
|
|
150
|
+
at = seek(hay, old, cursor, c.eof);
|
|
151
|
+
}
|
|
152
|
+
if (at === -1) {
|
|
153
|
+
return { error: `${label}: could not find these lines to replace:\n${c.old.slice(0, 6).map((l) => ` ${l}`).join('\n')}\nRead the file again and copy the lines exactly.` };
|
|
154
|
+
}
|
|
155
|
+
replacements.push([at, old.length, neu]);
|
|
156
|
+
cursor = at + old.length;
|
|
157
|
+
}
|
|
158
|
+
replacements.sort((a, b) => a[0] - b[0]);
|
|
159
|
+
const out = hay.slice();
|
|
160
|
+
for (let k = replacements.length - 1; k >= 0; k--) {
|
|
161
|
+
const [at, len, neu] = replacements[k];
|
|
162
|
+
out.splice(at, len, ...neu);
|
|
163
|
+
}
|
|
164
|
+
let result = out.join('\n') + (trailingNewline || !original ? '\n' : '');
|
|
165
|
+
if (crlf) result = result.replace(/\n/g, '\r\n');
|
|
166
|
+
return { content: result };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Work out what a patch would do, without touching the disk.
|
|
171
|
+
* @param {string} patchText
|
|
172
|
+
* @param {(path:string)=>string|null} readFile current content of a project path, null if it doesn't exist
|
|
173
|
+
* @returns {{changes: Array<{kind:'add'|'update'|'delete'|'move', path:string, newPath?:string, before:string, after:string, added:number, removed:number}>}|{error:string}}
|
|
174
|
+
*/
|
|
175
|
+
function planPatch(patchText, readFile) {
|
|
176
|
+
const parsed = parsePatch(patchText);
|
|
177
|
+
if (parsed.error) return { error: parsed.error };
|
|
178
|
+
const changes = [];
|
|
179
|
+
const virtual = new Map(); // paths changed earlier in this same patch
|
|
180
|
+
const current = (p) => (virtual.has(p) ? virtual.get(p) : readFile(p));
|
|
181
|
+
for (const h of parsed.hunks) {
|
|
182
|
+
if (h.type === 'add') {
|
|
183
|
+
const existing = current(h.path);
|
|
184
|
+
if (existing != null && existing !== '') return { error: `Cannot add ${h.path}: it already exists. Use "*** Update File:" to change it.` };
|
|
185
|
+
changes.push({ kind: 'add', path: h.path, before: '', after: h.contents, added: h.contents.split('\n').length - 1, removed: 0 });
|
|
186
|
+
virtual.set(h.path, h.contents);
|
|
187
|
+
} else if (h.type === 'delete') {
|
|
188
|
+
const existing = current(h.path);
|
|
189
|
+
if (existing == null) return { error: `Cannot delete ${h.path}: no such file.` };
|
|
190
|
+
changes.push({ kind: 'delete', path: h.path, before: existing, after: '', added: 0, removed: existing.split('\n').length });
|
|
191
|
+
virtual.set(h.path, null);
|
|
192
|
+
} else {
|
|
193
|
+
const existing = current(h.path);
|
|
194
|
+
if (existing == null) return { error: `Cannot update ${h.path}: no such file.` };
|
|
195
|
+
let after = existing;
|
|
196
|
+
if (h.chunks.length) {
|
|
197
|
+
const r = applyChunks(existing, h.chunks, h.path);
|
|
198
|
+
if (r.error) return { error: r.error };
|
|
199
|
+
after = r.content;
|
|
200
|
+
}
|
|
201
|
+
const added = h.chunks.reduce((n, c) => n + c.new.length, 0);
|
|
202
|
+
const removed = h.chunks.reduce((n, c) => n + c.old.length, 0);
|
|
203
|
+
if (h.movePath) {
|
|
204
|
+
if (current(h.movePath) != null) return { error: `Cannot move ${h.path} to ${h.movePath}: that file already exists.` };
|
|
205
|
+
changes.push({ kind: 'move', path: h.path, newPath: h.movePath, before: existing, after, added, removed });
|
|
206
|
+
virtual.set(h.path, null);
|
|
207
|
+
virtual.set(h.movePath, after);
|
|
208
|
+
} else {
|
|
209
|
+
changes.push({ kind: 'update', path: h.path, before: existing, after, added, removed });
|
|
210
|
+
virtual.set(h.path, after);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { changes };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { parsePatch, planPatch };
|