plugsuits 1.0.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/cli.js +3 -0
- package/dist/agent-reasoning-default.test.d.ts +2 -0
- package/dist/agent-reasoning-default.test.d.ts.map +1 -0
- package/dist/agent-reasoning-default.test.js +38 -0
- package/dist/agent-reasoning-default.test.js.map +1 -0
- package/dist/agent.d.ts +61 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +308 -0
- package/dist/agent.js.map +1 -0
- package/dist/agent.test.d.ts +2 -0
- package/dist/agent.test.d.ts.map +1 -0
- package/dist/agent.test.js +38 -0
- package/dist/agent.test.js.map +1 -0
- package/dist/cli-args.d.ts +15 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +63 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-args.test.d.ts +2 -0
- package/dist/cli-args.test.d.ts.map +1 -0
- package/dist/cli-args.test.js +105 -0
- package/dist/cli-args.test.js.map +1 -0
- package/dist/commands/aliases-and-tool-fallback.test.d.ts +2 -0
- package/dist/commands/aliases-and-tool-fallback.test.d.ts.map +1 -0
- package/dist/commands/aliases-and-tool-fallback.test.js +132 -0
- package/dist/commands/aliases-and-tool-fallback.test.js.map +1 -0
- package/dist/commands/clear.d.ts +3 -0
- package/dist/commands/clear.d.ts.map +1 -0
- package/dist/commands/clear.js +12 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/factories/create-toggle-command.d.ts +13 -0
- package/dist/commands/factories/create-toggle-command.d.ts.map +1 -0
- package/dist/commands/factories/create-toggle-command.js +38 -0
- package/dist/commands/factories/create-toggle-command.js.map +1 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +23 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +82 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/model.d.ts +15 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +100 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/reasoning-mode.d.ts +3 -0
- package/dist/commands/reasoning-mode.d.ts.map +1 -0
- package/dist/commands/reasoning-mode.js +47 -0
- package/dist/commands/reasoning-mode.js.map +1 -0
- package/dist/commands/render.d.ts +19 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +140 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/commands/render.test.d.ts +2 -0
- package/dist/commands/render.test.d.ts.map +1 -0
- package/dist/commands/render.test.js +36 -0
- package/dist/commands/render.test.js.map +1 -0
- package/dist/commands/tool-fallback.d.ts +3 -0
- package/dist/commands/tool-fallback.d.ts.map +1 -0
- package/dist/commands/tool-fallback.js +38 -0
- package/dist/commands/tool-fallback.js.map +1 -0
- package/dist/commands/translate.d.ts +3 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +12 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/translate.test.d.ts +2 -0
- package/dist/commands/translate.test.d.ts.map +1 -0
- package/dist/commands/translate.test.js +49 -0
- package/dist/commands/translate.test.js.map +1 -0
- package/dist/commands/types.d.ts +17 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/context/environment-context.d.ts +2 -0
- package/dist/context/environment-context.d.ts.map +1 -0
- package/dist/context/environment-context.js +11 -0
- package/dist/context/environment-context.js.map +1 -0
- package/dist/context/paths.d.ts +3 -0
- package/dist/context/paths.d.ts.map +1 -0
- package/dist/context/paths.js +3 -0
- package/dist/context/paths.js.map +1 -0
- package/dist/context/session.d.ts +4 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +16 -0
- package/dist/context/session.js.map +1 -0
- package/dist/context/skill-command-prefix.d.ts +4 -0
- package/dist/context/skill-command-prefix.d.ts.map +1 -0
- package/dist/context/skill-command-prefix.js +15 -0
- package/dist/context/skill-command-prefix.js.map +1 -0
- package/dist/context/skills-integration.test.d.ts +2 -0
- package/dist/context/skills-integration.test.d.ts.map +1 -0
- package/dist/context/skills-integration.test.js +87 -0
- package/dist/context/skills-integration.test.js.map +1 -0
- package/dist/context/skills.d.ts +18 -0
- package/dist/context/skills.d.ts.map +1 -0
- package/dist/context/skills.js +431 -0
- package/dist/context/skills.js.map +1 -0
- package/dist/context/skills.test.d.ts +2 -0
- package/dist/context/skills.test.d.ts.map +1 -0
- package/dist/context/skills.test.js +20 -0
- package/dist/context/skills.test.js.map +1 -0
- package/dist/context/system-prompt.d.ts +2 -0
- package/dist/context/system-prompt.d.ts.map +1 -0
- package/dist/context/system-prompt.js +100 -0
- package/dist/context/system-prompt.js.map +1 -0
- package/dist/context/translation-integration.test.d.ts +2 -0
- package/dist/context/translation-integration.test.d.ts.map +1 -0
- package/dist/context/translation-integration.test.js +138 -0
- package/dist/context/translation-integration.test.js.map +1 -0
- package/dist/context/translation.d.ts +21 -0
- package/dist/context/translation.d.ts.map +1 -0
- package/dist/context/translation.js +82 -0
- package/dist/context/translation.js.map +1 -0
- package/dist/context/translation.test.d.ts +2 -0
- package/dist/context/translation.test.d.ts.map +1 -0
- package/dist/context/translation.test.js +129 -0
- package/dist/context/translation.test.js.map +1 -0
- package/dist/entrypoints/cli-input-rendering.test.d.ts +2 -0
- package/dist/entrypoints/cli-input-rendering.test.d.ts.map +1 -0
- package/dist/entrypoints/cli-input-rendering.test.js +192 -0
- package/dist/entrypoints/cli-input-rendering.test.js.map +1 -0
- package/dist/entrypoints/cli.d.ts +3 -0
- package/dist/entrypoints/cli.d.ts.map +1 -0
- package/dist/entrypoints/cli.js +1268 -0
- package/dist/entrypoints/cli.js.map +1 -0
- package/dist/entrypoints/headless-agent-config.d.ts +21 -0
- package/dist/entrypoints/headless-agent-config.d.ts.map +1 -0
- package/dist/entrypoints/headless-agent-config.js +15 -0
- package/dist/entrypoints/headless-agent-config.js.map +1 -0
- package/dist/entrypoints/headless-agent-config.test.d.ts +2 -0
- package/dist/entrypoints/headless-agent-config.test.d.ts.map +1 -0
- package/dist/entrypoints/headless-agent-config.test.js +63 -0
- package/dist/entrypoints/headless-agent-config.test.js.map +1 -0
- package/dist/entrypoints/headless.d.ts +3 -0
- package/dist/entrypoints/headless.d.ts.map +1 -0
- package/dist/entrypoints/headless.js +396 -0
- package/dist/entrypoints/headless.js.map +1 -0
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +14 -0
- package/dist/env.js.map +1 -0
- package/dist/friendli-models.d.ts +21 -0
- package/dist/friendli-models.d.ts.map +1 -0
- package/dist/friendli-models.js +57 -0
- package/dist/friendli-models.js.map +1 -0
- package/dist/friendli-reasoning.d.ts +10 -0
- package/dist/friendli-reasoning.d.ts.map +1 -0
- package/dist/friendli-reasoning.js +181 -0
- package/dist/friendli-reasoning.js.map +1 -0
- package/dist/friendli-reasoning.test.d.ts +2 -0
- package/dist/friendli-reasoning.test.d.ts.map +1 -0
- package/dist/friendli-reasoning.test.js +77 -0
- package/dist/friendli-reasoning.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction/colors.d.ts +22 -0
- package/dist/interaction/colors.d.ts.map +1 -0
- package/dist/interaction/colors.js +24 -0
- package/dist/interaction/colors.js.map +1 -0
- package/dist/interaction/pi-tui-stream-renderer.d.ts +19 -0
- package/dist/interaction/pi-tui-stream-renderer.d.ts.map +1 -0
- package/dist/interaction/pi-tui-stream-renderer.js +1509 -0
- package/dist/interaction/pi-tui-stream-renderer.js.map +1 -0
- package/dist/interaction/pi-tui-stream-renderer.test.d.ts +2 -0
- package/dist/interaction/pi-tui-stream-renderer.test.d.ts.map +1 -0
- package/dist/interaction/pi-tui-stream-renderer.test.js +1314 -0
- package/dist/interaction/pi-tui-stream-renderer.test.js.map +1 -0
- package/dist/interaction/spinner.d.ts +13 -0
- package/dist/interaction/spinner.d.ts.map +1 -0
- package/dist/interaction/spinner.js +51 -0
- package/dist/interaction/spinner.js.map +1 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +15 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/todo-continuation.d.ts +11 -0
- package/dist/middleware/todo-continuation.d.ts.map +1 -0
- package/dist/middleware/todo-continuation.js +103 -0
- package/dist/middleware/todo-continuation.js.map +1 -0
- package/dist/middleware/trim-leading-newlines.d.ts +3 -0
- package/dist/middleware/trim-leading-newlines.d.ts.map +1 -0
- package/dist/middleware/trim-leading-newlines.js +49 -0
- package/dist/middleware/trim-leading-newlines.js.map +1 -0
- package/dist/reasoning-mode.d.ts +5 -0
- package/dist/reasoning-mode.d.ts.map +1 -0
- package/dist/reasoning-mode.js +30 -0
- package/dist/reasoning-mode.js.map +1 -0
- package/dist/reasoning-mode.test.d.ts +2 -0
- package/dist/reasoning-mode.test.d.ts.map +1 -0
- package/dist/reasoning-mode.test.js +22 -0
- package/dist/reasoning-mode.test.js.map +1 -0
- package/dist/tool-fallback-mode.d.ts +6 -0
- package/dist/tool-fallback-mode.d.ts.map +1 -0
- package/dist/tool-fallback-mode.js +25 -0
- package/dist/tool-fallback-mode.js.map +1 -0
- package/dist/tools/execute/shell-execute.d.ts +17 -0
- package/dist/tools/execute/shell-execute.d.ts.map +1 -0
- package/dist/tools/execute/shell-execute.js +55 -0
- package/dist/tools/execute/shell-execute.js.map +1 -0
- package/dist/tools/execute/shell-execute.test.d.ts +2 -0
- package/dist/tools/execute/shell-execute.test.d.ts.map +1 -0
- package/dist/tools/execute/shell-execute.test.js +86 -0
- package/dist/tools/execute/shell-execute.test.js.map +1 -0
- package/dist/tools/execute/shell-interact.d.ts +10 -0
- package/dist/tools/execute/shell-interact.d.ts.map +1 -0
- package/dist/tools/execute/shell-interact.js +122 -0
- package/dist/tools/execute/shell-interact.js.map +1 -0
- package/dist/tools/execute/shell-interact.test.d.ts +2 -0
- package/dist/tools/execute/shell-interact.test.d.ts.map +1 -0
- package/dist/tools/execute/shell-interact.test.js +175 -0
- package/dist/tools/execute/shell-interact.test.js.map +1 -0
- package/dist/tools/explore/glob.d.ts +15 -0
- package/dist/tools/explore/glob.d.ts.map +1 -0
- package/dist/tools/explore/glob.js +107 -0
- package/dist/tools/explore/glob.js.map +1 -0
- package/dist/tools/explore/glob.test.d.ts +2 -0
- package/dist/tools/explore/glob.test.d.ts.map +1 -0
- package/dist/tools/explore/glob.test.js +183 -0
- package/dist/tools/explore/glob.test.js.map +1 -0
- package/dist/tools/explore/grep.d.ts +27 -0
- package/dist/tools/explore/grep.d.ts.map +1 -0
- package/dist/tools/explore/grep.js +203 -0
- package/dist/tools/explore/grep.js.map +1 -0
- package/dist/tools/explore/grep.test.d.ts +2 -0
- package/dist/tools/explore/grep.test.d.ts.map +1 -0
- package/dist/tools/explore/grep.test.js +132 -0
- package/dist/tools/explore/grep.test.js.map +1 -0
- package/dist/tools/explore/read-file.d.ts +23 -0
- package/dist/tools/explore/read-file.d.ts.map +1 -0
- package/dist/tools/explore/read-file.js +84 -0
- package/dist/tools/explore/read-file.js.map +1 -0
- package/dist/tools/explore/read-file.test.d.ts +2 -0
- package/dist/tools/explore/read-file.test.d.ts.map +1 -0
- package/dist/tools/explore/read-file.test.js +278 -0
- package/dist/tools/explore/read-file.test.js.map +1 -0
- package/dist/tools/index.d.ts +71 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +26 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/modify/delete-file.d.ts +19 -0
- package/dist/tools/modify/delete-file.d.ts.map +1 -0
- package/dist/tools/modify/delete-file.js +71 -0
- package/dist/tools/modify/delete-file.js.map +1 -0
- package/dist/tools/modify/delete-file.test.d.ts +2 -0
- package/dist/tools/modify/delete-file.test.d.ts.map +1 -0
- package/dist/tools/modify/delete-file.test.js +136 -0
- package/dist/tools/modify/delete-file.test.js.map +1 -0
- package/dist/tools/modify/edit-file-diagnostics.d.ts +17 -0
- package/dist/tools/modify/edit-file-diagnostics.d.ts.map +1 -0
- package/dist/tools/modify/edit-file-diagnostics.js +157 -0
- package/dist/tools/modify/edit-file-diagnostics.js.map +1 -0
- package/dist/tools/modify/edit-file-repair.d.ts +13 -0
- package/dist/tools/modify/edit-file-repair.d.ts.map +1 -0
- package/dist/tools/modify/edit-file-repair.js +135 -0
- package/dist/tools/modify/edit-file-repair.js.map +1 -0
- package/dist/tools/modify/edit-file-stress.test.d.ts +2 -0
- package/dist/tools/modify/edit-file-stress.test.d.ts.map +1 -0
- package/dist/tools/modify/edit-file-stress.test.js +163 -0
- package/dist/tools/modify/edit-file-stress.test.js.map +1 -0
- package/dist/tools/modify/edit-file-validation.d.ts +9 -0
- package/dist/tools/modify/edit-file-validation.d.ts.map +1 -0
- package/dist/tools/modify/edit-file-validation.js +86 -0
- package/dist/tools/modify/edit-file-validation.js.map +1 -0
- package/dist/tools/modify/edit-file-whitespace.test.d.ts +2 -0
- package/dist/tools/modify/edit-file-whitespace.test.d.ts.map +1 -0
- package/dist/tools/modify/edit-file-whitespace.test.js +90 -0
- package/dist/tools/modify/edit-file-whitespace.test.js.map +1 -0
- package/dist/tools/modify/edit-file.d.ts +33 -0
- package/dist/tools/modify/edit-file.d.ts.map +1 -0
- package/dist/tools/modify/edit-file.js +177 -0
- package/dist/tools/modify/edit-file.js.map +1 -0
- package/dist/tools/modify/edit-file.test.d.ts +2 -0
- package/dist/tools/modify/edit-file.test.d.ts.map +1 -0
- package/dist/tools/modify/edit-file.test.js +948 -0
- package/dist/tools/modify/edit-file.test.js.map +1 -0
- package/dist/tools/modify/write-file.d.ts +17 -0
- package/dist/tools/modify/write-file.d.ts.map +1 -0
- package/dist/tools/modify/write-file.js +39 -0
- package/dist/tools/modify/write-file.js.map +1 -0
- package/dist/tools/modify/write-file.test.d.ts +2 -0
- package/dist/tools/modify/write-file.test.d.ts.map +1 -0
- package/dist/tools/modify/write-file.test.js +168 -0
- package/dist/tools/modify/write-file.test.js.map +1 -0
- package/dist/tools/planning/load-skill.d.ts +13 -0
- package/dist/tools/planning/load-skill.d.ts.map +1 -0
- package/dist/tools/planning/load-skill.js +101 -0
- package/dist/tools/planning/load-skill.js.map +1 -0
- package/dist/tools/planning/load-skill.test.d.ts +2 -0
- package/dist/tools/planning/load-skill.test.d.ts.map +1 -0
- package/dist/tools/planning/load-skill.test.js +37 -0
- package/dist/tools/planning/load-skill.test.js.map +1 -0
- package/dist/tools/planning/todo-write.d.ts +49 -0
- package/dist/tools/planning/todo-write.d.ts.map +1 -0
- package/dist/tools/planning/todo-write.js +118 -0
- package/dist/tools/planning/todo-write.js.map +1 -0
- package/dist/tools/planning/todo-write.test.d.ts +2 -0
- package/dist/tools/planning/todo-write.test.d.ts.map +1 -0
- package/dist/tools/planning/todo-write.test.js +82 -0
- package/dist/tools/planning/todo-write.test.js.map +1 -0
- package/dist/tools/utils/execute/format-utils.d.ts +4 -0
- package/dist/tools/utils/execute/format-utils.d.ts.map +1 -0
- package/dist/tools/utils/execute/format-utils.js +31 -0
- package/dist/tools/utils/execute/format-utils.js.map +1 -0
- package/dist/tools/utils/execute/format-utils.test.d.ts +2 -0
- package/dist/tools/utils/execute/format-utils.test.d.ts.map +1 -0
- package/dist/tools/utils/execute/format-utils.test.js +40 -0
- package/dist/tools/utils/execute/format-utils.test.js.map +1 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.d.ts +12 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.d.ts.map +1 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.js +269 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.js.map +1 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.test.d.ts +2 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.test.d.ts.map +1 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.test.js +233 -0
- package/dist/tools/utils/execute/noninteractive-wrapper.test.js.map +1 -0
- package/dist/tools/utils/execute/output-handler.d.ts +14 -0
- package/dist/tools/utils/execute/output-handler.d.ts.map +1 -0
- package/dist/tools/utils/execute/output-handler.js +71 -0
- package/dist/tools/utils/execute/output-handler.js.map +1 -0
- package/dist/tools/utils/execute/output-handler.test.d.ts +2 -0
- package/dist/tools/utils/execute/output-handler.test.d.ts.map +1 -0
- package/dist/tools/utils/execute/output-handler.test.js +58 -0
- package/dist/tools/utils/execute/output-handler.test.js.map +1 -0
- package/dist/tools/utils/execute/process-manager.d.ts +17 -0
- package/dist/tools/utils/execute/process-manager.d.ts.map +1 -0
- package/dist/tools/utils/execute/process-manager.js +229 -0
- package/dist/tools/utils/execute/process-manager.js.map +1 -0
- package/dist/tools/utils/execute/process-manager.test.d.ts +2 -0
- package/dist/tools/utils/execute/process-manager.test.d.ts.map +1 -0
- package/dist/tools/utils/execute/process-manager.test.js +139 -0
- package/dist/tools/utils/execute/process-manager.test.js.map +1 -0
- package/dist/tools/utils/execute/shell-detection.d.ts +3 -0
- package/dist/tools/utils/execute/shell-detection.d.ts.map +1 -0
- package/dist/tools/utils/execute/shell-detection.js +56 -0
- package/dist/tools/utils/execute/shell-detection.js.map +1 -0
- package/dist/tools/utils/execute/shell-detection.test.d.ts +2 -0
- package/dist/tools/utils/execute/shell-detection.test.d.ts.map +1 -0
- package/dist/tools/utils/execute/shell-detection.test.js +86 -0
- package/dist/tools/utils/execute/shell-detection.test.js.map +1 -0
- package/dist/tools/utils/hashline/autocorrect-replacement-lines.d.ts +5 -0
- package/dist/tools/utils/hashline/autocorrect-replacement-lines.d.ts.map +1 -0
- package/dist/tools/utils/hashline/autocorrect-replacement-lines.js +113 -0
- package/dist/tools/utils/hashline/autocorrect-replacement-lines.js.map +1 -0
- package/dist/tools/utils/hashline/constants.d.ts +5 -0
- package/dist/tools/utils/hashline/constants.d.ts.map +1 -0
- package/dist/tools/utils/hashline/constants.js +11 -0
- package/dist/tools/utils/hashline/constants.js.map +1 -0
- package/dist/tools/utils/hashline/diff-utils.d.ts +7 -0
- package/dist/tools/utils/hashline/diff-utils.d.ts.map +1 -0
- package/dist/tools/utils/hashline/diff-utils.js +50 -0
- package/dist/tools/utils/hashline/diff-utils.js.map +1 -0
- package/dist/tools/utils/hashline/diff-utils.test.d.ts +2 -0
- package/dist/tools/utils/hashline/diff-utils.test.d.ts.map +1 -0
- package/dist/tools/utils/hashline/diff-utils.test.js +46 -0
- package/dist/tools/utils/hashline/diff-utils.test.js.map +1 -0
- package/dist/tools/utils/hashline/edit-deduplication.d.ts +6 -0
- package/dist/tools/utils/hashline/edit-deduplication.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-deduplication.js +32 -0
- package/dist/tools/utils/hashline/edit-deduplication.js.map +1 -0
- package/dist/tools/utils/hashline/edit-operation-primitives.d.ts +11 -0
- package/dist/tools/utils/hashline/edit-operation-primitives.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-operation-primitives.js +93 -0
- package/dist/tools/utils/hashline/edit-operation-primitives.js.map +1 -0
- package/dist/tools/utils/hashline/edit-operations.d.ts +9 -0
- package/dist/tools/utils/hashline/edit-operations.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-operations.js +101 -0
- package/dist/tools/utils/hashline/edit-operations.js.map +1 -0
- package/dist/tools/utils/hashline/edit-operations.test.d.ts +2 -0
- package/dist/tools/utils/hashline/edit-operations.test.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-operations.test.js +135 -0
- package/dist/tools/utils/hashline/edit-operations.test.js.map +1 -0
- package/dist/tools/utils/hashline/edit-ordering.d.ts +5 -0
- package/dist/tools/utils/hashline/edit-ordering.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-ordering.js +54 -0
- package/dist/tools/utils/hashline/edit-ordering.js.map +1 -0
- package/dist/tools/utils/hashline/edit-text-normalization.d.ts +9 -0
- package/dist/tools/utils/hashline/edit-text-normalization.d.ts.map +1 -0
- package/dist/tools/utils/hashline/edit-text-normalization.js +135 -0
- package/dist/tools/utils/hashline/edit-text-normalization.js.map +1 -0
- package/dist/tools/utils/hashline/file-text-canonicalization.d.ts +8 -0
- package/dist/tools/utils/hashline/file-text-canonicalization.d.ts.map +1 -0
- package/dist/tools/utils/hashline/file-text-canonicalization.js +42 -0
- package/dist/tools/utils/hashline/file-text-canonicalization.js.map +1 -0
- package/dist/tools/utils/hashline/hash-computation.d.ts +14 -0
- package/dist/tools/utils/hashline/hash-computation.d.ts.map +1 -0
- package/dist/tools/utils/hashline/hash-computation.js +158 -0
- package/dist/tools/utils/hashline/hash-computation.js.map +1 -0
- package/dist/tools/utils/hashline/hash-computation.test.d.ts +2 -0
- package/dist/tools/utils/hashline/hash-computation.test.d.ts.map +1 -0
- package/dist/tools/utils/hashline/hash-computation.test.js +63 -0
- package/dist/tools/utils/hashline/hash-computation.test.js.map +1 -0
- package/dist/tools/utils/hashline/hashline-chunk-formatter.d.ts +11 -0
- package/dist/tools/utils/hashline/hashline-chunk-formatter.d.ts.map +1 -0
- package/dist/tools/utils/hashline/hashline-chunk-formatter.js +41 -0
- package/dist/tools/utils/hashline/hashline-chunk-formatter.js.map +1 -0
- package/dist/tools/utils/hashline/hashline-edit-diff.d.ts +2 -0
- package/dist/tools/utils/hashline/hashline-edit-diff.d.ts.map +1 -0
- package/dist/tools/utils/hashline/hashline-edit-diff.js +27 -0
- package/dist/tools/utils/hashline/hashline-edit-diff.js.map +1 -0
- package/dist/tools/utils/hashline/index.d.ts +14 -0
- package/dist/tools/utils/hashline/index.d.ts.map +1 -0
- package/dist/tools/utils/hashline/index.js +11 -0
- package/dist/tools/utils/hashline/index.js.map +1 -0
- package/dist/tools/utils/hashline/merge-expansion.d.ts +4 -0
- package/dist/tools/utils/hashline/merge-expansion.d.ts.map +1 -0
- package/dist/tools/utils/hashline/merge-expansion.js +105 -0
- package/dist/tools/utils/hashline/merge-expansion.js.map +1 -0
- package/dist/tools/utils/hashline/normalize-edits.d.ts +11 -0
- package/dist/tools/utils/hashline/normalize-edits.d.ts.map +1 -0
- package/dist/tools/utils/hashline/normalize-edits.js +81 -0
- package/dist/tools/utils/hashline/normalize-edits.js.map +1 -0
- package/dist/tools/utils/hashline/types.d.ts +18 -0
- package/dist/tools/utils/hashline/types.d.ts.map +1 -0
- package/dist/tools/utils/hashline/types.js +2 -0
- package/dist/tools/utils/hashline/types.js.map +1 -0
- package/dist/tools/utils/hashline/validation.d.ts +19 -0
- package/dist/tools/utils/hashline/validation.d.ts.map +1 -0
- package/dist/tools/utils/hashline/validation.js +161 -0
- package/dist/tools/utils/hashline/validation.js.map +1 -0
- package/dist/tools/utils/hashline/validation.test.d.ts +2 -0
- package/dist/tools/utils/hashline/validation.test.d.ts.map +1 -0
- package/dist/tools/utils/hashline/validation.test.js +86 -0
- package/dist/tools/utils/hashline/validation.test.js.map +1 -0
- package/dist/tools/utils/safety-utils.d.ts +66 -0
- package/dist/tools/utils/safety-utils.d.ts.map +1 -0
- package/dist/tools/utils/safety-utils.js +681 -0
- package/dist/tools/utils/safety-utils.js.map +1 -0
- package/dist/utils/tools-manager.d.ts +16 -0
- package/dist/utils/tools-manager.d.ts.map +1 -0
- package/dist/utils/tools-manager.js +257 -0
- package/dist/utils/tools-manager.js.map +1 -0
- package/package.json +49 -0
- package/src/AGENTS.md +52 -0
- package/src/agent-reasoning-default.test.ts +48 -0
- package/src/agent.test.ts +49 -0
- package/src/agent.ts +455 -0
- package/src/cli-args.test.ts +152 -0
- package/src/cli-args.ts +90 -0
- package/src/commands/aliases-and-tool-fallback.test.ts +172 -0
- package/src/commands/clear.ts +14 -0
- package/src/commands/factories/create-toggle-command.ts +68 -0
- package/src/commands/help.ts +30 -0
- package/src/commands/index.ts +125 -0
- package/src/commands/model.ts +146 -0
- package/src/commands/reasoning-mode.ts +55 -0
- package/src/commands/render.test.ts +47 -0
- package/src/commands/render.ts +205 -0
- package/src/commands/tool-fallback.ts +47 -0
- package/src/commands/translate.test.ts +62 -0
- package/src/commands/translate.ts +14 -0
- package/src/commands/types.ts +18 -0
- package/src/context/environment-context.ts +11 -0
- package/src/context/paths.ts +2 -0
- package/src/context/session.ts +18 -0
- package/src/context/skill-command-prefix.ts +18 -0
- package/src/context/skills-integration.test.ts +113 -0
- package/src/context/skills.test.ts +25 -0
- package/src/context/skills.ts +566 -0
- package/src/context/system-prompt.ts +100 -0
- package/src/context/translation-integration.test.ts +194 -0
- package/src/context/translation.test.ts +186 -0
- package/src/context/translation.ts +122 -0
- package/src/entrypoints/AGENTS.md +33 -0
- package/src/entrypoints/cli-input-rendering.test.ts +236 -0
- package/src/entrypoints/cli.ts +1845 -0
- package/src/entrypoints/headless-agent-config.test.ts +82 -0
- package/src/entrypoints/headless-agent-config.ts +42 -0
- package/src/entrypoints/headless.ts +622 -0
- package/src/env.ts +14 -0
- package/src/friendli-models.ts +81 -0
- package/src/friendli-reasoning.test.ts +147 -0
- package/src/friendli-reasoning.ts +280 -0
- package/src/index.ts +3 -0
- package/src/interaction/colors.ts +24 -0
- package/src/interaction/pi-tui-stream-renderer.test.ts +1471 -0
- package/src/interaction/pi-tui-stream-renderer.ts +2150 -0
- package/src/interaction/spinner.ts +61 -0
- package/src/middleware/index.ts +32 -0
- package/src/middleware/todo-continuation.ts +128 -0
- package/src/middleware/trim-leading-newlines.ts +66 -0
- package/src/reasoning-mode.test.ts +24 -0
- package/src/reasoning-mode.ts +40 -0
- package/src/skills/example/SKILL.md +44 -0
- package/src/skills/example/references/api.md +37 -0
- package/src/skills/example/scripts/setup.sh +13 -0
- package/src/skills/git-workflow.md +405 -0
- package/src/tool-fallback-mode.ts +34 -0
- package/src/tools/AGENTS.md +44 -0
- package/src/tools/execute/shell-execute.test.ts +114 -0
- package/src/tools/execute/shell-execute.ts +74 -0
- package/src/tools/execute/shell-execute.txt +27 -0
- package/src/tools/execute/shell-interact.test.ts +236 -0
- package/src/tools/execute/shell-interact.ts +151 -0
- package/src/tools/execute/shell-interact.txt +15 -0
- package/src/tools/explore/glob-files.txt +8 -0
- package/src/tools/explore/glob.test.ts +217 -0
- package/src/tools/explore/glob.ts +137 -0
- package/src/tools/explore/grep-files.txt +12 -0
- package/src/tools/explore/grep.test.ts +183 -0
- package/src/tools/explore/grep.ts +266 -0
- package/src/tools/explore/read-file.test.ts +355 -0
- package/src/tools/explore/read-file.ts +102 -0
- package/src/tools/explore/read-file.txt +24 -0
- package/src/tools/index.ts +29 -0
- package/src/tools/modify/AGENTS.md +38 -0
- package/src/tools/modify/delete-file.test.ts +200 -0
- package/src/tools/modify/delete-file.ts +95 -0
- package/src/tools/modify/delete-file.txt +9 -0
- package/src/tools/modify/edit-file-diagnostics.ts +210 -0
- package/src/tools/modify/edit-file-repair.ts +183 -0
- package/src/tools/modify/edit-file-stress.test.ts +200 -0
- package/src/tools/modify/edit-file-validation.ts +134 -0
- package/src/tools/modify/edit-file-whitespace.test.ts +117 -0
- package/src/tools/modify/edit-file.test.ts +1231 -0
- package/src/tools/modify/edit-file.ts +252 -0
- package/src/tools/modify/edit-file.txt +73 -0
- package/src/tools/modify/write-file.test.ts +240 -0
- package/src/tools/modify/write-file.ts +56 -0
- package/src/tools/modify/write-file.txt +9 -0
- package/src/tools/planning/load-skill.test.ts +48 -0
- package/src/tools/planning/load-skill.ts +136 -0
- package/src/tools/planning/load-skill.txt +6 -0
- package/src/tools/planning/todo-write.test.ts +91 -0
- package/src/tools/planning/todo-write.ts +141 -0
- package/src/tools/planning/todo-write.txt +7 -0
- package/src/tools/utils/execute/format-utils.test.ts +53 -0
- package/src/tools/utils/execute/format-utils.ts +37 -0
- package/src/tools/utils/execute/noninteractive-wrapper.test.ts +306 -0
- package/src/tools/utils/execute/noninteractive-wrapper.ts +314 -0
- package/src/tools/utils/execute/output-handler.test.ts +72 -0
- package/src/tools/utils/execute/output-handler.ts +101 -0
- package/src/tools/utils/execute/process-manager.test.ts +175 -0
- package/src/tools/utils/execute/process-manager.ts +310 -0
- package/src/tools/utils/execute/shell-detection.test.ts +112 -0
- package/src/tools/utils/execute/shell-detection.ts +72 -0
- package/src/tools/utils/hashline/autocorrect-replacement-lines.ts +159 -0
- package/src/tools/utils/hashline/constants.ts +13 -0
- package/src/tools/utils/hashline/diff-utils.test.ts +61 -0
- package/src/tools/utils/hashline/diff-utils.ts +64 -0
- package/src/tools/utils/hashline/edit-deduplication.ts +40 -0
- package/src/tools/utils/hashline/edit-operation-primitives.ts +149 -0
- package/src/tools/utils/hashline/edit-operations.test.ts +154 -0
- package/src/tools/utils/hashline/edit-operations.ts +132 -0
- package/src/tools/utils/hashline/edit-ordering.ts +60 -0
- package/src/tools/utils/hashline/edit-text-normalization.ts +180 -0
- package/src/tools/utils/hashline/file-text-canonicalization.ts +58 -0
- package/src/tools/utils/hashline/hash-computation.test.ts +82 -0
- package/src/tools/utils/hashline/hash-computation.ts +199 -0
- package/src/tools/utils/hashline/hashline-chunk-formatter.ts +61 -0
- package/src/tools/utils/hashline/hashline-edit-diff.ts +35 -0
- package/src/tools/utils/hashline/index.ts +55 -0
- package/src/tools/utils/hashline/merge-expansion.ts +120 -0
- package/src/tools/utils/hashline/normalize-edits.ts +127 -0
- package/src/tools/utils/hashline/types.ts +20 -0
- package/src/tools/utils/hashline/validation.test.ts +109 -0
- package/src/tools/utils/hashline/validation.ts +212 -0
- package/src/tools/utils/safety-utils.ts +938 -0
- package/src/utils/tools-manager.ts +353 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import {
|
|
6
|
+
applyHashlineEditsWithReport,
|
|
7
|
+
canonicalizeFileText,
|
|
8
|
+
type HashlineEdit,
|
|
9
|
+
normalizeHashlineEdits,
|
|
10
|
+
restoreFileText,
|
|
11
|
+
} from "../utils/hashline";
|
|
12
|
+
import { assertWriteSafety, safeAtomicWriteFile } from "../utils/safety-utils";
|
|
13
|
+
import EDIT_FILE_DESCRIPTION from "./edit-file.txt";
|
|
14
|
+
import { buildEscalationBailMessage } from "./edit-file-diagnostics";
|
|
15
|
+
import type { HashlineToolEdit } from "./edit-file-repair";
|
|
16
|
+
import {
|
|
17
|
+
assertExpectedFileHash,
|
|
18
|
+
canCreateFromMissingFile,
|
|
19
|
+
validateAndRepairEdits,
|
|
20
|
+
} from "./edit-file-validation";
|
|
21
|
+
|
|
22
|
+
const hashlineToolEditSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
op: z
|
|
25
|
+
.enum(["replace", "append", "prepend"])
|
|
26
|
+
.describe("replace/append/prepend use hashline anchors."),
|
|
27
|
+
pos: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe(
|
|
31
|
+
"Line anchor from read_file (format: {line_number}#{hash_id})."
|
|
32
|
+
),
|
|
33
|
+
end: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(
|
|
37
|
+
"Range end anchor for replace operations (format: {line_number}#{hash_id})."
|
|
38
|
+
),
|
|
39
|
+
lines: z
|
|
40
|
+
.union([z.array(z.string()), z.string(), z.null()])
|
|
41
|
+
.describe(
|
|
42
|
+
"Replacement content. string[] for new lines, string for single line, null or [] to delete."
|
|
43
|
+
),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
|
|
47
|
+
// Lenient schema: lines optional — fallback when model omits lines
|
|
48
|
+
// Custom validation in validateAndRepairEdits provides better error messages than ZodError
|
|
49
|
+
const lenientEditSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
op: z.enum(["replace", "append", "prepend"]),
|
|
52
|
+
pos: z.string().optional(),
|
|
53
|
+
end: z.string().optional(),
|
|
54
|
+
lines: z.union([z.array(z.string()), z.string(), z.null()]).optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
|
|
58
|
+
const lenientInputSchema = z
|
|
59
|
+
.object({
|
|
60
|
+
path: z.string(),
|
|
61
|
+
edits: z.array(lenientEditSchema).min(1),
|
|
62
|
+
expected_file_hash: z.string().optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
|
|
66
|
+
const inputSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
path: z.string().describe("The path to the file"),
|
|
69
|
+
edits: z
|
|
70
|
+
.array(hashlineToolEditSchema)
|
|
71
|
+
.min(1)
|
|
72
|
+
.describe("Hashline-native edit operations."),
|
|
73
|
+
expected_file_hash: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Optional stale-check file hash from read_file output."),
|
|
77
|
+
})
|
|
78
|
+
.strict();
|
|
79
|
+
|
|
80
|
+
export type EditFileInput = z.input<typeof lenientInputSchema>;
|
|
81
|
+
|
|
82
|
+
export interface EditFileOptions {
|
|
83
|
+
/** Override project root for safety checks (defaults to process.cwd()). */
|
|
84
|
+
rootDir?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// validateAndRepairEdits, canCreateFromMissingFile, assertExpectedFileHash
|
|
88
|
+
// moved to ./edit-file-validation.ts
|
|
89
|
+
|
|
90
|
+
function formatResult(params: {
|
|
91
|
+
created: boolean;
|
|
92
|
+
editCount: number;
|
|
93
|
+
lineCountDelta: number;
|
|
94
|
+
path: string;
|
|
95
|
+
warningLines?: string[];
|
|
96
|
+
}): string {
|
|
97
|
+
const action = params.created ? "Created" : "Updated";
|
|
98
|
+
const output: string[] = [`${action} ${params.path}`];
|
|
99
|
+
const summaryParts: string[] = [`${params.editCount} edit(s) applied`];
|
|
100
|
+
if (params.lineCountDelta !== 0) {
|
|
101
|
+
const sign = params.lineCountDelta > 0 ? "+" : "";
|
|
102
|
+
summaryParts.push(`${sign}${params.lineCountDelta} line(s)`);
|
|
103
|
+
}
|
|
104
|
+
output.push(summaryParts.join(", "));
|
|
105
|
+
if (params.warningLines && params.warningLines.length > 0) {
|
|
106
|
+
output.push("");
|
|
107
|
+
output.push("Warnings:");
|
|
108
|
+
output.push(...params.warningLines);
|
|
109
|
+
}
|
|
110
|
+
return output.join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function ensureParentDir(path: string): Promise<void> {
|
|
114
|
+
const directory = dirname(path);
|
|
115
|
+
if (directory !== ".") {
|
|
116
|
+
await mkdir(directory, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function readExistingContent(path: string): Promise<{
|
|
121
|
+
content: string;
|
|
122
|
+
exists: boolean;
|
|
123
|
+
}> {
|
|
124
|
+
try {
|
|
125
|
+
return {
|
|
126
|
+
content: await readFile(path, "utf-8"),
|
|
127
|
+
exists: true,
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (
|
|
131
|
+
error instanceof Error &&
|
|
132
|
+
"code" in error &&
|
|
133
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
134
|
+
) {
|
|
135
|
+
return {
|
|
136
|
+
content: "",
|
|
137
|
+
exists: false,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex validation logic
|
|
145
|
+
export async function executeEditFile(input: EditFileInput, options?: EditFileOptions): Promise<string> {
|
|
146
|
+
let parsed: z.infer<typeof inputSchema>;
|
|
147
|
+
try {
|
|
148
|
+
parsed = inputSchema.parse(input);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (
|
|
151
|
+
error instanceof z.ZodError &&
|
|
152
|
+
error.issues.some(
|
|
153
|
+
(issue) => issue.path.length >= 2 && issue.path.at(-1) === "lines"
|
|
154
|
+
)
|
|
155
|
+
) {
|
|
156
|
+
// Fall back to lenient parse — custom validation gives better error messages
|
|
157
|
+
parsed = lenientInputSchema.parse(input) as z.infer<typeof inputSchema>;
|
|
158
|
+
} else {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// C-1 + C-2: Path traversal and symlink safety checks
|
|
164
|
+
const safePath = await assertWriteSafety(parsed.path, options?.rootDir);
|
|
165
|
+
|
|
166
|
+
const { content: rawContent, exists } = await readExistingContent(
|
|
167
|
+
safePath
|
|
168
|
+
);
|
|
169
|
+
const oldEnvelope = canonicalizeFileText(rawContent);
|
|
170
|
+
const fileLines = exists ? oldEnvelope.content.split("\n") : [];
|
|
171
|
+
|
|
172
|
+
let repairWarnings: string[];
|
|
173
|
+
let normalizedEdits: HashlineEdit[];
|
|
174
|
+
try {
|
|
175
|
+
const validateResult = validateAndRepairEdits(
|
|
176
|
+
parsed.edits as HashlineToolEdit[],
|
|
177
|
+
fileLines,
|
|
178
|
+
parsed.path
|
|
179
|
+
);
|
|
180
|
+
repairWarnings = validateResult.repairWarnings;
|
|
181
|
+
normalizedEdits = normalizeHashlineEdits(validateResult.edits);
|
|
182
|
+
} catch (parseError) {
|
|
183
|
+
if (
|
|
184
|
+
parseError instanceof Error &&
|
|
185
|
+
parseError.message.includes("explicit 'lines'")
|
|
186
|
+
) {
|
|
187
|
+
const bailMessage = buildEscalationBailMessage(
|
|
188
|
+
parsed.edits,
|
|
189
|
+
parsed.path,
|
|
190
|
+
fileLines
|
|
191
|
+
);
|
|
192
|
+
if (bailMessage) {
|
|
193
|
+
return bailMessage;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw parseError;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!(exists || canCreateFromMissingFile(normalizedEdits))) {
|
|
200
|
+
throw new Error(`File not found: ${parsed.path}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
assertExpectedFileHash(parsed.expected_file_hash, rawContent);
|
|
204
|
+
|
|
205
|
+
const applyResult = applyHashlineEditsWithReport(
|
|
206
|
+
oldEnvelope.content,
|
|
207
|
+
normalizedEdits
|
|
208
|
+
);
|
|
209
|
+
const canonicalNewContent = applyResult.content;
|
|
210
|
+
|
|
211
|
+
if (canonicalNewContent === oldEnvelope.content) {
|
|
212
|
+
let diagnostic = `No changes made to ${parsed.path}. The edits produced identical content.`;
|
|
213
|
+
if (applyResult.noopEdits > 0) {
|
|
214
|
+
diagnostic += ` No-op edits: ${applyResult.noopEdits}. Re-read the file and provide content that differs from current lines.`;
|
|
215
|
+
}
|
|
216
|
+
return `Error: ${diagnostic}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const writeContent = restoreFileText(canonicalNewContent, oldEnvelope);
|
|
220
|
+
|
|
221
|
+
await ensureParentDir(safePath);
|
|
222
|
+
await safeAtomicWriteFile(safePath, writeContent);
|
|
223
|
+
|
|
224
|
+
const originalLineCount = rawContent.split("\n").length;
|
|
225
|
+
const newLineCount = writeContent.split("\n").length;
|
|
226
|
+
|
|
227
|
+
const allWarnings: string[] = [...repairWarnings];
|
|
228
|
+
if (applyResult.noopEdits > 0) {
|
|
229
|
+
allWarnings.push(
|
|
230
|
+
`${applyResult.noopEdits} edit(s) were no-ops because replacement text matched existing content.`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (applyResult.deduplicatedEdits > 0) {
|
|
234
|
+
allWarnings.push(
|
|
235
|
+
`${applyResult.deduplicatedEdits} duplicate edit(s) were removed.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return formatResult({
|
|
240
|
+
created: !exists,
|
|
241
|
+
editCount: parsed.edits.length,
|
|
242
|
+
lineCountDelta: newLineCount - originalLineCount,
|
|
243
|
+
path: parsed.path,
|
|
244
|
+
warningLines: allWarnings.length > 0 ? allWarnings : undefined,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export const editFileTool = tool({
|
|
249
|
+
description: EDIT_FILE_DESCRIPTION,
|
|
250
|
+
inputSchema,
|
|
251
|
+
execute: (input) => executeEditFile(input),
|
|
252
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Applies precise file edits using `LINE#ID` tags from `read_file` output.
|
|
2
|
+
|
|
3
|
+
**Workflow**:
|
|
4
|
+
1. Call `read_file` to get fresh `{line_number}#{hash_id}|TEXT` anchors.
|
|
5
|
+
2. Pick the smallest operation per change site.
|
|
6
|
+
3. Call `edit_file` once per file with all operations.
|
|
7
|
+
4. Re-read the file before issuing another edit on the same path.
|
|
8
|
+
|
|
9
|
+
**Input**: `{ path, edits[], expected_file_hash? }`
|
|
10
|
+
|
|
11
|
+
**Operations**:
|
|
12
|
+
Every edit has `op` and `lines`. Most edits also have `pos`; range replaces add `end`.
|
|
13
|
+
Both `pos` and `end` use `"N#ID"` format (e.g. `"23#XY"`).
|
|
14
|
+
|
|
15
|
+
`pos` — the anchor line:
|
|
16
|
+
- `replace`: the line to replace (or start of range)
|
|
17
|
+
- `prepend`: insert new lines before this line
|
|
18
|
+
- `append`: insert new lines after this line
|
|
19
|
+
|
|
20
|
+
`end` — range replace only. The last line of the range (inclusive).
|
|
21
|
+
|
|
22
|
+
`lines` — the replacement content (REQUIRED for every edit):
|
|
23
|
+
- `["line1", "line2"]` — replace with these lines
|
|
24
|
+
- `"line1"` — shorthand for single line
|
|
25
|
+
- `null` or `[]` — delete the line(s) entirely
|
|
26
|
+
|
|
27
|
+
**Line or range replace/delete**:
|
|
28
|
+
- `{ op: "replace", pos: "N#ID", lines: ["new content"] }` — replace one line
|
|
29
|
+
- `{ op: "replace", pos: "N#ID", end: "M#ID", lines: ["new"] }` — replace range
|
|
30
|
+
- `{ op: "replace", pos: "N#ID", lines: null }` — delete one line
|
|
31
|
+
- `{ op: "replace", pos: "N#ID", end: "M#ID", lines: null }` — delete range
|
|
32
|
+
|
|
33
|
+
**Insert new lines**:
|
|
34
|
+
- `{ op: "prepend", pos: "N#ID", lines: ["before"] }` — insert before line
|
|
35
|
+
- `{ op: "append", pos: "N#ID", lines: ["after"] }` — insert after line
|
|
36
|
+
- `{ op: "append", lines: ["at end"] }` — insert at end of file (no pos)
|
|
37
|
+
- `{ op: "prepend", lines: ["at start"] }` — insert at start of file (no pos)
|
|
38
|
+
|
|
39
|
+
**Rules**:
|
|
40
|
+
- Anchor to structural lines (function signatures, braces), not blank lines.
|
|
41
|
+
- Blank-line hashes are position-dependent and change after edits.
|
|
42
|
+
- Prefer insertion at structure boundaries over rewriting neighbors.
|
|
43
|
+
- Allowed keys per edit: `op`, `pos`, `end`, `lines` — nothing else. `lines` is always required.
|
|
44
|
+
|
|
45
|
+
**Recovery**:
|
|
46
|
+
- Tag mismatch (`>>>`): retry using fresh tags from the error snippet.
|
|
47
|
+
- No-op (`identical`): re-read target lines and adjust the edit.
|
|
48
|
+
|
|
49
|
+
**Examples**:
|
|
50
|
+
```
|
|
51
|
+
edit_file({
|
|
52
|
+
path: "config.ts",
|
|
53
|
+
edits: [{ op: "replace", pos: "12#AB", lines: ["port: 8080,"] }]
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
edit_file({
|
|
57
|
+
path: "routes.ts",
|
|
58
|
+
edits: [
|
|
59
|
+
{ op: "prepend", pos: "41#MQ", lines: [" // new route"] },
|
|
60
|
+
{ op: "append", pos: "41#MQ", lines: [" app.get('/health', handler);"] }
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
edit_file({
|
|
65
|
+
path: "log.ts",
|
|
66
|
+
edits: [{ op: "replace", pos: "3#QH", end: "5#KR", lines: ["// consolidated"] }]
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
edit_file({
|
|
70
|
+
path: "old.ts",
|
|
71
|
+
edits: [{ op: "replace", pos: "7#TX", lines: null }]
|
|
72
|
+
})
|
|
73
|
+
```
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
symlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { executeWriteFile } from "./write-file";
|
|
13
|
+
|
|
14
|
+
describe("executeWriteFile", () => {
|
|
15
|
+
let tempDir: string;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
tempDir = mkdtempSync(join(tmpdir(), "write-file-test-"));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
if (existsSync(tempDir)) {
|
|
23
|
+
rmSync(tempDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("basic write operations", () => {
|
|
28
|
+
it("creates new file and returns metadata", async () => {
|
|
29
|
+
const testFile = join(tempDir, "new.txt");
|
|
30
|
+
const content = "line1\nline2\nline3";
|
|
31
|
+
|
|
32
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
33
|
+
|
|
34
|
+
expect(result).toContain("OK - created new.txt");
|
|
35
|
+
expect(result).toContain("bytes:");
|
|
36
|
+
expect(result).toContain("lines: 3");
|
|
37
|
+
expect(result).not.toContain("(preview)");
|
|
38
|
+
expect(result).not.toContain("========");
|
|
39
|
+
|
|
40
|
+
const written = readFileSync(testFile, "utf-8");
|
|
41
|
+
expect(written).toBe(content);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("overwrites existing file and indicates action", async () => {
|
|
45
|
+
const testFile = join(tempDir, "existing.txt");
|
|
46
|
+
writeFileSync(testFile, "old content");
|
|
47
|
+
|
|
48
|
+
const newContent = "new content";
|
|
49
|
+
const result = await executeWriteFile(
|
|
50
|
+
{ path: testFile, content: newContent },
|
|
51
|
+
{ rootDir: tempDir }
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(result).toContain("OK - overwrote existing.txt");
|
|
55
|
+
expect(result).not.toContain("new content");
|
|
56
|
+
|
|
57
|
+
const written = readFileSync(testFile, "utf-8");
|
|
58
|
+
expect(written).toBe(newContent);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("creates parent directories automatically", async () => {
|
|
62
|
+
const nestedFile = join(tempDir, "deep", "nested", "dir", "file.txt");
|
|
63
|
+
const content = "nested content";
|
|
64
|
+
|
|
65
|
+
const result = await executeWriteFile({ path: nestedFile, content }, { rootDir: tempDir });
|
|
66
|
+
|
|
67
|
+
expect(result).toContain("OK - created file.txt");
|
|
68
|
+
expect(existsSync(nestedFile)).toBe(true);
|
|
69
|
+
|
|
70
|
+
const written = readFileSync(nestedFile, "utf-8");
|
|
71
|
+
expect(written).toBe(content);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("output formatting", () => {
|
|
76
|
+
it("does not include content for small files", async () => {
|
|
77
|
+
const testFile = join(tempDir, "small.txt");
|
|
78
|
+
const content = "a\nb\nc\nd\ne";
|
|
79
|
+
|
|
80
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
81
|
+
|
|
82
|
+
expect(result).toContain("bytes:");
|
|
83
|
+
expect(result).toContain("lines: 5");
|
|
84
|
+
expect(result).not.toContain("a\nb\nc\nd\ne");
|
|
85
|
+
expect(result).not.toContain("(preview)");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("does not include content for large files", async () => {
|
|
89
|
+
const testFile = join(tempDir, "large.txt");
|
|
90
|
+
const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`);
|
|
91
|
+
const content = lines.join("\n");
|
|
92
|
+
|
|
93
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
94
|
+
|
|
95
|
+
expect(result).toContain("bytes:");
|
|
96
|
+
expect(result).toContain("lines: 20");
|
|
97
|
+
expect(result).not.toContain("line1");
|
|
98
|
+
expect(result).not.toContain("(preview)");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("includes correct byte count", async () => {
|
|
102
|
+
const testFile = join(tempDir, "bytes.txt");
|
|
103
|
+
const content = "hello";
|
|
104
|
+
|
|
105
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
106
|
+
|
|
107
|
+
expect(result).toContain("bytes: 5");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("handles unicode content correctly", async () => {
|
|
111
|
+
const testFile = join(tempDir, "unicode.txt");
|
|
112
|
+
const content = "한글 테스트\n이모지 🎉";
|
|
113
|
+
|
|
114
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
115
|
+
|
|
116
|
+
expect(result).not.toContain("한글 테스트");
|
|
117
|
+
expect(result).not.toContain("이모지 🎉");
|
|
118
|
+
expect(result).toContain("lines: 2");
|
|
119
|
+
|
|
120
|
+
const written = readFileSync(testFile, "utf-8");
|
|
121
|
+
expect(written).toBe(content);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("edge cases", () => {
|
|
126
|
+
it("handles empty content", async () => {
|
|
127
|
+
const testFile = join(tempDir, "empty.txt");
|
|
128
|
+
|
|
129
|
+
const result = await executeWriteFile({ path: testFile, content: "" }, { rootDir: tempDir });
|
|
130
|
+
|
|
131
|
+
expect(result).toContain("OK - created empty.txt");
|
|
132
|
+
expect(result).toContain("bytes: 0");
|
|
133
|
+
expect(result).toContain("lines: 1");
|
|
134
|
+
|
|
135
|
+
const written = readFileSync(testFile, "utf-8");
|
|
136
|
+
expect(written).toBe("");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("handles single line content", async () => {
|
|
140
|
+
const testFile = join(tempDir, "single.txt");
|
|
141
|
+
const content = "single line without newline";
|
|
142
|
+
|
|
143
|
+
const result = await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
144
|
+
|
|
145
|
+
expect(result).toContain("lines: 1");
|
|
146
|
+
expect(result).not.toContain("single line without newline");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("handles content with special characters", async () => {
|
|
150
|
+
const testFile = join(tempDir, "special.txt");
|
|
151
|
+
const content = `const x = { a: 1, b: "test" };\nconst y = \`template \${x}\`;`;
|
|
152
|
+
|
|
153
|
+
await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
154
|
+
|
|
155
|
+
const written = readFileSync(testFile, "utf-8");
|
|
156
|
+
expect(written).toBe(content);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("file safety (C-1, C-2, H-1)", () => {
|
|
161
|
+
it("C-1: blocks path traversal via .. segments", async () => {
|
|
162
|
+
const traversalPath = join(tempDir, "..", "..", "etc", "passwd");
|
|
163
|
+
await expect(
|
|
164
|
+
executeWriteFile({ path: traversalPath, content: "malicious" }, { rootDir: tempDir })
|
|
165
|
+
).rejects.toThrow(/[Pp]ath traversal blocked/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("C-1: blocks absolute paths outside project root", async () => {
|
|
169
|
+
await expect(
|
|
170
|
+
executeWriteFile({ path: "/tmp/outside-project.txt", content: "bad" }, { rootDir: tempDir })
|
|
171
|
+
).rejects.toThrow(/[Pp]ath traversal blocked|outside/);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("C-2: blocks writes through symlinks", async () => {
|
|
175
|
+
const realFile = join(tempDir, "real-target.txt");
|
|
176
|
+
writeFileSync(realFile, "original");
|
|
177
|
+
const symlinkPath = join(tempDir, "symlink-to-real.txt");
|
|
178
|
+
symlinkSync(realFile, symlinkPath);
|
|
179
|
+
|
|
180
|
+
await expect(
|
|
181
|
+
executeWriteFile({ path: symlinkPath, content: "through symlink" }, { rootDir: tempDir })
|
|
182
|
+
).rejects.toThrow(/symlink/i);
|
|
183
|
+
|
|
184
|
+
// Original file should be unchanged
|
|
185
|
+
expect(readFileSync(realFile, "utf-8")).toBe("original");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("C-2: blocks writes through symlinks pointing outside root", async () => {
|
|
189
|
+
const outsideDir = mkdtempSync(join(tmpdir(), "outside-root-"));
|
|
190
|
+
const outsideFile = join(outsideDir, "secret.txt");
|
|
191
|
+
writeFileSync(outsideFile, "secret data");
|
|
192
|
+
const symlinkPath = join(tempDir, "escape-link.txt");
|
|
193
|
+
symlinkSync(outsideFile, symlinkPath);
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
await expect(
|
|
197
|
+
executeWriteFile({ path: symlinkPath, content: "overwrite" }, { rootDir: tempDir })
|
|
198
|
+
).rejects.toThrow(/symlink/i);
|
|
199
|
+
expect(readFileSync(outsideFile, "utf-8")).toBe("secret data");
|
|
200
|
+
} finally {
|
|
201
|
+
rmSync(outsideDir, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("H-1: atomic write produces correct content (no partial writes)", async () => {
|
|
206
|
+
const testFile = join(tempDir, "atomic-test.txt");
|
|
207
|
+
const content = "line1\nline2\nline3";
|
|
208
|
+
|
|
209
|
+
await executeWriteFile({ path: testFile, content }, { rootDir: tempDir });
|
|
210
|
+
|
|
211
|
+
const written = readFileSync(testFile, "utf-8");
|
|
212
|
+
expect(written).toBe(content);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("H-1: no temp files left after successful write", async () => {
|
|
216
|
+
const testFile = join(tempDir, "no-temp-residue.txt");
|
|
217
|
+
await executeWriteFile({ path: testFile, content: "clean" }, { rootDir: tempDir });
|
|
218
|
+
|
|
219
|
+
const dirEntries = readFileSync(testFile, "utf-8");
|
|
220
|
+
expect(dirEntries).toBe("clean");
|
|
221
|
+
|
|
222
|
+
// Check no .tmp- files remain in the directory
|
|
223
|
+
const { readdirSync } = require("node:fs");
|
|
224
|
+
const files: string[] = readdirSync(tempDir);
|
|
225
|
+
const tmpFiles = files.filter((f: string) => f.includes(".tmp-"));
|
|
226
|
+
expect(tmpFiles.length).toBe(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("allows writes within project root (normal operation)", async () => {
|
|
230
|
+
const safeFile = join(tempDir, "safe-subdir", "nested.txt");
|
|
231
|
+
const result = await executeWriteFile(
|
|
232
|
+
{ path: safeFile, content: "safe content" },
|
|
233
|
+
{ rootDir: tempDir }
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(result).toContain("OK - created nested.txt");
|
|
237
|
+
expect(readFileSync(safeFile, "utf-8")).toBe("safe content");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname } from "node:path";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { assertWriteSafety, safeAtomicWriteFile } from "../utils/safety-utils";
|
|
6
|
+
import WRITE_FILE_DESCRIPTION from "./write-file.txt";
|
|
7
|
+
|
|
8
|
+
const inputSchema = z.object({
|
|
9
|
+
path: z.string().describe("File path (absolute or relative)"),
|
|
10
|
+
content: z.string().describe("Content to write"),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type WriteFileInput = z.infer<typeof inputSchema>;
|
|
14
|
+
|
|
15
|
+
export interface WriteFileOptions {
|
|
16
|
+
/** Override project root for safety checks (defaults to process.cwd()). */
|
|
17
|
+
rootDir?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function executeWriteFile(
|
|
21
|
+
{ path, content }: WriteFileInput,
|
|
22
|
+
options?: WriteFileOptions
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
// C-1 + C-2: Path traversal and symlink safety checks
|
|
25
|
+
const safePath = await assertWriteSafety(path, options?.rootDir);
|
|
26
|
+
|
|
27
|
+
const dir = dirname(safePath);
|
|
28
|
+
if (dir !== ".") {
|
|
29
|
+
await mkdir(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// H-1: Atomic write with O_EXCL temp file + rename.
|
|
33
|
+
// safeAtomicWriteFile handles existence check (lstat), symlink rejection,
|
|
34
|
+
// crypto-random temp names, O_EXCL to prevent pre-creation attacks,
|
|
35
|
+
// and POSIX rename() which does not follow symlinks.
|
|
36
|
+
const { existed } = await safeAtomicWriteFile(safePath, content);
|
|
37
|
+
|
|
38
|
+
const lines = content.split("\n");
|
|
39
|
+
const lineCount = lines.length;
|
|
40
|
+
const byteCount = Buffer.byteLength(content, "utf-8");
|
|
41
|
+
const fileName = basename(path);
|
|
42
|
+
const action = existed ? "overwrote" : "created";
|
|
43
|
+
|
|
44
|
+
const output = [
|
|
45
|
+
`OK - ${action} ${fileName}`,
|
|
46
|
+
`bytes: ${byteCount}, lines: ${lineCount}`,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
return output.join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const writeFileTool = tool({
|
|
53
|
+
description: WRITE_FILE_DESCRIPTION,
|
|
54
|
+
inputSchema,
|
|
55
|
+
execute: (input) => executeWriteFile(input),
|
|
56
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### write_file
|
|
2
|
+
**Purpose**: Create new file or completely overwrite existing file
|
|
3
|
+
|
|
4
|
+
**When to use**:
|
|
5
|
+
- Creating new files from scratch
|
|
6
|
+
- Replacing most or all contents of a file
|
|
7
|
+
- Writing generated code/configuration
|
|
8
|
+
|
|
9
|
+
**DO NOT use shell**: Prefer this over `echo >`, `cat <<EOF`, `printf >`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { executeLoadSkill } from "./load-skill";
|
|
3
|
+
|
|
4
|
+
describe("executeLoadSkill", () => {
|
|
5
|
+
it("loads git-workflow skill successfully", async () => {
|
|
6
|
+
const result = await executeLoadSkill({ skillName: "git-workflow" });
|
|
7
|
+
|
|
8
|
+
expect(result).toContain("# Skill Loaded: git-workflow");
|
|
9
|
+
expect(result).toContain("# Git Workflow Skill");
|
|
10
|
+
expect(result).toContain("gh pr create");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns error for non-existent skill", async () => {
|
|
14
|
+
const result = await executeLoadSkill({ skillName: "non-existent-skill" });
|
|
15
|
+
|
|
16
|
+
expect(result).toContain("Error: Skill 'non-existent-skill' not found");
|
|
17
|
+
expect(result).toContain("system prompt");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("loads skill with frontmatter", async () => {
|
|
21
|
+
const result = await executeLoadSkill({ skillName: "git-workflow" });
|
|
22
|
+
|
|
23
|
+
expect(result).toContain("name: Git Workflow");
|
|
24
|
+
expect(result).toContain("description:");
|
|
25
|
+
expect(result).toContain("triggers:");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("loads skill with prompts prefix", async () => {
|
|
29
|
+
const result = await executeLoadSkill({ skillName: "prompts:example" });
|
|
30
|
+
|
|
31
|
+
expect(result).toContain("# Skill Loaded: prompts:example");
|
|
32
|
+
expect(result).toContain("# Example Skill");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("rejects path traversal attempts with ..", async () => {
|
|
36
|
+
const result = await executeLoadSkill({ skillName: "../../../etc/passwd" });
|
|
37
|
+
|
|
38
|
+
expect(result).toContain("Error: Invalid skill name");
|
|
39
|
+
expect(result).toContain("cannot contain '..' or '/'");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("rejects path traversal attempts with /", async () => {
|
|
43
|
+
const result = await executeLoadSkill({ skillName: "foo/bar" });
|
|
44
|
+
|
|
45
|
+
expect(result).toContain("Error: Invalid skill name");
|
|
46
|
+
expect(result).toContain("cannot contain '..' or '/'");
|
|
47
|
+
});
|
|
48
|
+
});
|