pi-usereq 0.4.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/.g.conf +20 -0
- package/.github/workflows/release-npm.yml +131 -0
- package/.gitignore +270 -0
- package/CHANGELOG.md +221 -0
- package/LICENSE +674 -0
- package/README.md +260 -0
- package/TODO.md +20 -0
- package/docs/pi.dev/agent-document-manifest.json +6399 -0
- package/docs/pi.dev/coding-agent-docs/compaction.md +394 -0
- package/docs/pi.dev/coding-agent-docs/custom-provider.md +596 -0
- package/docs/pi.dev/coding-agent-docs/development.md +71 -0
- package/docs/pi.dev/coding-agent-docs/extensions.md +2262 -0
- package/docs/pi.dev/coding-agent-docs/images/doom-extension.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/exy.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/interactive-mode.png +0 -0
- package/docs/pi.dev/coding-agent-docs/images/tree-view.png +0 -0
- package/docs/pi.dev/coding-agent-docs/json.md +82 -0
- package/docs/pi.dev/coding-agent-docs/keybindings.md +175 -0
- package/docs/pi.dev/coding-agent-docs/models.md +392 -0
- package/docs/pi.dev/coding-agent-docs/packages.md +218 -0
- package/docs/pi.dev/coding-agent-docs/prompt-templates.md +67 -0
- package/docs/pi.dev/coding-agent-docs/providers.md +195 -0
- package/docs/pi.dev/coding-agent-docs/rpc.md +1377 -0
- package/docs/pi.dev/coding-agent-docs/sdk.md +1124 -0
- package/docs/pi.dev/coding-agent-docs/session.md +412 -0
- package/docs/pi.dev/coding-agent-docs/settings.md +247 -0
- package/docs/pi.dev/coding-agent-docs/shell-aliases.md +13 -0
- package/docs/pi.dev/coding-agent-docs/skills.md +232 -0
- package/docs/pi.dev/coding-agent-docs/terminal-setup.md +106 -0
- package/docs/pi.dev/coding-agent-docs/termux.md +127 -0
- package/docs/pi.dev/coding-agent-docs/themes.md +295 -0
- package/docs/pi.dev/coding-agent-docs/tmux.md +61 -0
- package/docs/pi.dev/coding-agent-docs/tree.md +231 -0
- package/docs/pi.dev/coding-agent-docs/tui.md +887 -0
- package/docs/pi.dev/coding-agent-docs/windows.md +17 -0
- package/docs/pi.dev/mom-docs/artifacts-server.md +475 -0
- package/docs/pi.dev/mom-docs/events.md +307 -0
- package/docs/pi.dev/mom-docs/new.md +970 -0
- package/docs/pi.dev/mom-docs/sandbox.md +153 -0
- package/docs/pi.dev/mom-docs/slack-bot-minimal-guide.md +399 -0
- package/docs/pi.dev/mom-docs/v86.md +319 -0
- package/docs/pi.dev/pods-docs/gml-4.5.md +189 -0
- package/docs/pi.dev/pods-docs/gpt-oss.md +233 -0
- package/docs/pi.dev/pods-docs/implementation-plan.md +183 -0
- package/docs/pi.dev/pods-docs/kimi-k2.md +197 -0
- package/docs/pi.dev/pods-docs/models.md +116 -0
- package/docs/pi.dev/pods-docs/plan.md +166 -0
- package/docs/pi.dev/pods-docs/qwen3-coder.md +132 -0
- package/images/flowchart-bw.png +0 -0
- package/images/flowchart-bw.svg +102 -0
- package/images/flowchart.md +100 -0
- package/images/flowchart.png +0 -0
- package/images/flowchart.svg +3 -0
- package/package.json +46 -0
- package/req/docs/REFERENCES.md +4554 -0
- package/req/docs/REQUIREMENTS.md +475 -0
- package/req/docs/WORKFLOW.md +1059 -0
- package/scripts/debug-extension.ts +497 -0
- package/scripts/lib/extension-debug-harness.ts +450 -0
- package/scripts/lib/recording-extension-api.ts +786 -0
- package/scripts/lib/sdk-smoke.ts +503 -0
- package/scripts/pi-usereq-debug.sh +330 -0
- package/scripts/tool-args-to-params.ts +208 -0
- package/src/cli.ts +349 -0
- package/src/core/agent-tool-json.ts +621 -0
- package/src/core/compress-files.ts +72 -0
- package/src/core/compress-payload.ts +648 -0
- package/src/core/compress.ts +464 -0
- package/src/core/config.ts +272 -0
- package/src/core/doxygen-parser.ts +318 -0
- package/src/core/errors.ts +31 -0
- package/src/core/extension-status.ts +660 -0
- package/src/core/find-constructs.ts +319 -0
- package/src/core/find-payload.ts +915 -0
- package/src/core/generate-markdown.ts +120 -0
- package/src/core/path-context.ts +196 -0
- package/src/core/pi-notify.ts +430 -0
- package/src/core/pi-usereq-tools.ts +140 -0
- package/src/core/prompts.ts +184 -0
- package/src/core/reference-payload.ts +818 -0
- package/src/core/resources.ts +63 -0
- package/src/core/runtime-project-paths.ts +99 -0
- package/src/core/settings-menu.ts +233 -0
- package/src/core/source-analyzer.ts +1721 -0
- package/src/core/static-check.ts +674 -0
- package/src/core/token-counter.ts +729 -0
- package/src/core/tool-runner.ts +717 -0
- package/src/core/utils.ts +185 -0
- package/src/index.ts +2209 -0
- package/src/resources/guidelines/Google_C++_Style_Guide.md +3711 -0
- package/src/resources/guidelines/Google_Python_Style_Guide.md +3709 -0
- package/src/resources/prompts/analyze.md +130 -0
- package/src/resources/prompts/change.md +227 -0
- package/src/resources/prompts/check.md +139 -0
- package/src/resources/prompts/cover.md +219 -0
- package/src/resources/prompts/create.md +104 -0
- package/src/resources/prompts/fix.md +221 -0
- package/src/resources/prompts/flowchart.md +220 -0
- package/src/resources/prompts/implement.md +163 -0
- package/src/resources/prompts/new.md +226 -0
- package/src/resources/prompts/readme.md +182 -0
- package/src/resources/prompts/recreate.md +213 -0
- package/src/resources/prompts/refactor.md +213 -0
- package/src/resources/prompts/references.md +100 -0
- package/src/resources/prompts/renumber.md +119 -0
- package/src/resources/prompts/workflow.md +202 -0
- package/src/resources/prompts/write.md +99 -0
- package/src/resources/sounds/Machine-alert-beep-sound-effect.mp3 +0 -0
- package/src/resources/sounds/Soft-high-tech-notification-sound-effect.mp3 +0 -0
- package/src/resources/templates/Document_Source_Code_in_Doxygen_Style.md +130 -0
- package/src/resources/templates/HDT_Test_Authoring_Guide.md +318 -0
- package/src/resources/templates/Requirements_Template.md +78 -0
- package/tests/attended-results-scenarios.ts +758 -0
- package/tests/attended-results.test.ts +39 -0
- package/tests/cli-command-option-parity.test.ts +815 -0
- package/tests/debug-extension-harness.test.ts +463 -0
- package/tests/extension-registration.test.ts +2006 -0
- package/tests/fixtures/fixture_c.c +361 -0
- package/tests/fixtures/fixture_cpp.cpp +407 -0
- package/tests/fixtures/fixture_csharp.cs +411 -0
- package/tests/fixtures/fixture_elixir.ex +409 -0
- package/tests/fixtures/fixture_go.go +341 -0
- package/tests/fixtures/fixture_haskell.hs +250 -0
- package/tests/fixtures/fixture_java.java +430 -0
- package/tests/fixtures/fixture_javascript.js +383 -0
- package/tests/fixtures/fixture_kotlin.kt +451 -0
- package/tests/fixtures/fixture_lua.lua +276 -0
- package/tests/fixtures/fixture_perl.pl +310 -0
- package/tests/fixtures/fixture_php.php +433 -0
- package/tests/fixtures/fixture_python.py +502 -0
- package/tests/fixtures/fixture_ruby.rb +345 -0
- package/tests/fixtures/fixture_rust.rs +380 -0
- package/tests/fixtures/fixture_scala.scala +398 -0
- package/tests/fixtures/fixture_shell.sh +276 -0
- package/tests/fixtures/fixture_swift.swift +397 -0
- package/tests/fixtures/fixture_typescript.ts +434 -0
- package/tests/fixtures/fixture_zig.zig +295 -0
- package/tests/fixtures_attended_results/project/compress-line-numbers.json +5 -0
- package/tests/fixtures_attended_results/project/compress.json +5 -0
- package/tests/fixtures_attended_results/project/enable-static-check-invalid-command.json +5 -0
- package/tests/fixtures_attended_results/project/enable-static-check-valid.json +5 -0
- package/tests/fixtures_attended_results/project/files-static-check.json +5 -0
- package/tests/fixtures_attended_results/project/find-line-numbers.json +5 -0
- package/tests/fixtures_attended_results/project/find.json +5 -0
- package/tests/fixtures_attended_results/project/get-base-path.json +5 -0
- package/tests/fixtures_attended_results/project/git-check-clean.json +5 -0
- package/tests/fixtures_attended_results/project/git-check-dirty.json +5 -0
- package/tests/fixtures_attended_results/project/git-path.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-create-invalid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-create-valid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-delete-nonexistent.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-delete-valid.json +5 -0
- package/tests/fixtures_attended_results/project/git-wt-name.json +5 -0
- package/tests/fixtures_attended_results/project/references.json +5 -0
- package/tests/fixtures_attended_results/project/static-check.json +5 -0
- package/tests/fixtures_attended_results/project/tokens.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-compress-line-numbers/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-find-line-numbers/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-references/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/files-tokens/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-command/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-dummy/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +5 -0
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +5 -0
- package/tests/helpers.ts +204 -0
- package/tests/oracle-project.test.ts +63 -0
- package/tests/oracle-standalone.test.ts +48 -0
- package/tests/prompt-rendering.test.ts +66 -0
- package/tests/release-workflow.test.ts +133 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Implements the executable back-end for all pi-usereq CLI and extension tools.
|
|
4
|
+
* @details Centralizes project discovery, git helpers, source-file collection, documentation generation, compression, construct lookup, static-check dispatch, and worktree lifecycle operations. Runtime depends on the selected command and may include filesystem reads, config writes, process spawning, and git mutations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { ReqError } from "./errors.js";
|
|
11
|
+
import { loadConfig, normalizeConfigPaths, saveConfig, type UseReqConfig } from "./config.js";
|
|
12
|
+
import { formatRuntimePathForDisplay } from "./path-context.js";
|
|
13
|
+
import { resolveRuntimeGitPath } from "./runtime-project-paths.js";
|
|
14
|
+
import { countFilesMetrics, formatPackSummary } from "./token-counter.js";
|
|
15
|
+
import {
|
|
16
|
+
buildReferenceToolExecutionStderr,
|
|
17
|
+
buildReferenceToolPayload,
|
|
18
|
+
} from "./reference-payload.js";
|
|
19
|
+
import { compressFiles } from "./compress-files.js";
|
|
20
|
+
import { findConstructsInFiles } from "./find-constructs.js";
|
|
21
|
+
import { STATIC_CHECK_EXT_TO_LANG, dispatchStaticCheckForFile } from "./static-check.js";
|
|
22
|
+
import { makeRelativeIfContainsProject } from "./utils.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @brief Represents the normalized output contract for a tool invocation.
|
|
26
|
+
* @details Every tool emits stdout, stderr, and a numeric exit code so CLI and extension front-ends can handle results uniformly. The interface is compile-time only and adds no runtime cost.
|
|
27
|
+
*/
|
|
28
|
+
export interface ToolResult {
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
code: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @brief Lists directory names excluded during source-file collection.
|
|
36
|
+
* @details The set is currently empty but remains configurable as a centralized filter point for future exclusions. Membership checks are O(1).
|
|
37
|
+
*/
|
|
38
|
+
export const EXCLUDED_DIRS = new Set<string>();
|
|
39
|
+
/**
|
|
40
|
+
* @brief Lists source-file extensions accepted by collection and tool workflows.
|
|
41
|
+
* @details Derived from static-check language support so every collected source file can be mapped to a canonical language. Membership checks are O(1).
|
|
42
|
+
*/
|
|
43
|
+
export const SUPPORTED_EXTENSIONS = new Set(Object.keys(STATIC_CHECK_EXT_TO_LANG));
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @brief Creates a successful tool result payload.
|
|
47
|
+
* @details Wraps stdout and stderr text with exit code `0`. Runtime is O(1). No side effects occur.
|
|
48
|
+
* @param[in] stdout {string} Standard-output text.
|
|
49
|
+
* @param[in] stderr {string} Standard-error text.
|
|
50
|
+
* @return {ToolResult} Successful result object.
|
|
51
|
+
*/
|
|
52
|
+
function ok(stdout = "", stderr = ""): ToolResult {
|
|
53
|
+
return { stdout, stderr, code: 0 };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @brief Throws a `ReqError` populated with tool-result stream content.
|
|
58
|
+
* @details Creates a structured failure object, attaches optional stdout and stderr payloads, and throws immediately. Runtime is O(1). Side effect: throws an exception.
|
|
59
|
+
* @param[in] message {string} Primary failure message.
|
|
60
|
+
* @param[in] code {number} Exit code to attach. Defaults to `1`.
|
|
61
|
+
* @param[in] stdout {string} Optional stdout payload.
|
|
62
|
+
* @param[in] stderr {string} Optional stderr payload. Defaults to `message` when omitted.
|
|
63
|
+
* @return {never} This function never returns.
|
|
64
|
+
* @throws {ReqError} Always throws.
|
|
65
|
+
*/
|
|
66
|
+
function fail(message: string, code = 1, stdout = "", stderr = ""): never {
|
|
67
|
+
const error = new ReqError(message, code);
|
|
68
|
+
(error as ReqError & { stdout?: string; stderr?: string }).stdout = stdout;
|
|
69
|
+
(error as ReqError & { stdout?: string; stderr?: string }).stderr = stderr || message;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @brief Executes a subprocess synchronously and captures its output.
|
|
75
|
+
* @details Delegates to `spawnSync`, passes through an optional working directory, and forces UTF-8 decoding. Runtime is dominated by external process execution. Side effects include process spawning.
|
|
76
|
+
* @param[in] command {string[]} Executable plus argument vector.
|
|
77
|
+
* @param[in] options {{ cwd?: string }} Optional process-spawn settings.
|
|
78
|
+
* @return {ReturnType<typeof spawnSync>} Captured subprocess result.
|
|
79
|
+
*/
|
|
80
|
+
function runCapture(command: string[], options: { cwd?: string } = {}) {
|
|
81
|
+
return spawnSync(command[0]!, command.slice(1), {
|
|
82
|
+
cwd: options.cwd,
|
|
83
|
+
encoding: "utf8",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @brief Resolves the effective runtime git root for the current base path.
|
|
89
|
+
* @details Delegates to the shared runtime-only repository resolver so git helpers never consult persisted `git-path` metadata. Runtime is dominated by git probing. Side effects include subprocess execution.
|
|
90
|
+
* @param[in] projectBase {string} Resolved base path.
|
|
91
|
+
* @return {string | undefined} Effective git root path or `undefined` when unavailable.
|
|
92
|
+
* @satisfies REQ-145, REQ-146
|
|
93
|
+
*/
|
|
94
|
+
function resolveEffectiveGitPath(projectBase: string): string | undefined {
|
|
95
|
+
return resolveRuntimeGitPath(projectBase);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @brief Rewrites a branch name into a filesystem-safe token.
|
|
100
|
+
* @details Replaces characters invalid for worktree directory and branch-name generation with `-`. Runtime is O(n). No side effects occur.
|
|
101
|
+
* @param[in] branch {string} Raw branch name.
|
|
102
|
+
* @return {string} Sanitized token.
|
|
103
|
+
*/
|
|
104
|
+
export function sanitizeBranchName(branch: string): string {
|
|
105
|
+
return branch.replace(/[<>:"/\\|?*\x00-\x1f\s~^{}\[\]]/g, "-");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @brief Validates a requested worktree or branch name.
|
|
110
|
+
* @details Rejects empty names, dot-path markers, whitespace, and filesystem-invalid characters. Runtime is O(n). No side effects occur.
|
|
111
|
+
* @param[in] wtName {string} Candidate worktree name.
|
|
112
|
+
* @return {boolean} `true` when the name is acceptable for worktree creation.
|
|
113
|
+
*/
|
|
114
|
+
export function validateWtName(wtName: string): boolean {
|
|
115
|
+
if (!wtName || wtName === "." || wtName === "..") return false;
|
|
116
|
+
return !/[<>:"/\\|?*\x00-\x1f\s]/.test(wtName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @brief Collects tracked and untracked source files from configured source directories.
|
|
121
|
+
* @details Uses `git ls-files` to enumerate candidate files, filters them by configured source roots, excluded directories, and supported extensions, and returns sorted absolute paths. Runtime is O(n log n) in collected file count plus git execution cost. Side effects include process spawning.
|
|
122
|
+
* @param[in] srcDirs {string[]} Configured source-directory roots.
|
|
123
|
+
* @param[in] projectBase {string} Absolute project root.
|
|
124
|
+
* @return {string[]} Sorted absolute source-file paths.
|
|
125
|
+
* @throws {ReqError} Throws when `git ls-files` fails.
|
|
126
|
+
*/
|
|
127
|
+
export function collectSourceFiles(srcDirs: string[], projectBase: string): string[] {
|
|
128
|
+
const result = runCapture(
|
|
129
|
+
["git", "-C", projectBase, "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
130
|
+
{ cwd: projectBase },
|
|
131
|
+
);
|
|
132
|
+
if (result.error || result.status !== 0) {
|
|
133
|
+
fail("Error: failed to collect source files with `git ls-files` in project root.", 1);
|
|
134
|
+
}
|
|
135
|
+
const normalizedSrcDirs = srcDirs.map((srcDir) => makeRelativeIfContainsProject(srcDir, projectBase).split(path.sep).join("/").replace(/^\.?\/?/, "").replace(/\/+$/, ""));
|
|
136
|
+
const collected = new Set<string>();
|
|
137
|
+
for (const relPathRaw of result.stdout.split(/\r?\n/)) {
|
|
138
|
+
let relPath = relPathRaw.trim().replace(/^\.\//, "");
|
|
139
|
+
if (!relPath) continue;
|
|
140
|
+
if (
|
|
141
|
+
!normalizedSrcDirs.some(
|
|
142
|
+
(srcDir) => srcDir === "" || srcDir === "." || relPath === srcDir || relPath.startsWith(`${srcDir}/`),
|
|
143
|
+
)
|
|
144
|
+
) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const relObj = relPath.split("/");
|
|
148
|
+
if (relObj.slice(0, -1).some((part) => EXCLUDED_DIRS.has(part))) continue;
|
|
149
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
150
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
151
|
+
collected.add(path.resolve(projectBase, relPath));
|
|
152
|
+
}
|
|
153
|
+
return [...collected].sort();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @brief Builds an ASCII tree from relative file paths.
|
|
158
|
+
* @details Materializes a nested object tree and renders it using box-drawing characters for markdown display. Runtime is O(n log n) in path count due to sorting. No side effects occur.
|
|
159
|
+
* @param[in] paths {string[]} Relative POSIX-style file paths.
|
|
160
|
+
* @return {string} Rendered ASCII tree.
|
|
161
|
+
*/
|
|
162
|
+
function buildAsciiTree(paths: string[]): string {
|
|
163
|
+
const tree: Record<string, Record<string, unknown> | null> = {};
|
|
164
|
+
for (const relPath of [...paths].sort()) {
|
|
165
|
+
let node = tree;
|
|
166
|
+
const parts = relPath.split("/");
|
|
167
|
+
parts.forEach((part, index) => {
|
|
168
|
+
const isLeaf = index === parts.length - 1;
|
|
169
|
+
if (isLeaf) {
|
|
170
|
+
node[part] ??= null;
|
|
171
|
+
} else {
|
|
172
|
+
node[part] = (node[part] as Record<string, unknown> | null) ?? {};
|
|
173
|
+
node = node[part] as Record<string, Record<string, unknown> | null>;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const lines = ["."];
|
|
178
|
+
const emit = (branch: Record<string, Record<string, unknown> | null>, prefix = "") => {
|
|
179
|
+
const entries = Object.entries(branch).sort(([a], [b]) => a.localeCompare(b));
|
|
180
|
+
entries.forEach(([name, child], index) => {
|
|
181
|
+
const last = index === entries.length - 1;
|
|
182
|
+
lines.push(`${prefix}${last ? "└── " : "├── "}${name}`);
|
|
183
|
+
if (child && Object.keys(child).length > 0) {
|
|
184
|
+
emit(child as Record<string, Record<string, unknown> | null>, `${prefix}${last ? " " : "│ "}`);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
emit(tree);
|
|
189
|
+
return lines.join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @brief Formats the collected file structure as markdown.
|
|
194
|
+
* @details Converts absolute file paths to project-relative POSIX paths, renders an ASCII tree, and wraps the result in a fenced markdown block. Runtime is O(n log n) in file count. No side effects occur.
|
|
195
|
+
* @param[in] files {string[]} Absolute file paths.
|
|
196
|
+
* @param[in] projectBase {string} Absolute project root.
|
|
197
|
+
* @return {string} Markdown section describing the file structure.
|
|
198
|
+
*/
|
|
199
|
+
function formatFilesStructureMarkdown(files: string[], projectBase: string): string {
|
|
200
|
+
const relativePaths = files.map((filePath) => path.relative(projectBase, filePath).split(path.sep).join("/"));
|
|
201
|
+
return `# Files Structure\n\`\`\`\n${buildAsciiTree(relativePaths)}\n\`\`\``;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @brief Resolves and validates the project base directory.
|
|
206
|
+
* @details Uses the supplied path or the current working directory, normalizes it to an absolute path, and verifies that it exists. Runtime is O(1) plus one filesystem existence check. Side effects are limited to filesystem reads.
|
|
207
|
+
* @param[in] projectBase {string | undefined} Optional project-root override.
|
|
208
|
+
* @return {string} Absolute validated project root.
|
|
209
|
+
* @throws {ReqError} Throws when the resolved path does not exist.
|
|
210
|
+
*/
|
|
211
|
+
export function resolveProjectBase(projectBase?: string): string {
|
|
212
|
+
const base = projectBase ? path.resolve(projectBase) : process.cwd();
|
|
213
|
+
if (!fs.existsSync(base)) {
|
|
214
|
+
fail(`Error: PROJECT_BASE '${base}' does not exist`, 2);
|
|
215
|
+
}
|
|
216
|
+
return base;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @brief Resolves the project base and effective source-directory list.
|
|
221
|
+
* @details Loads configuration when not supplied, validates that at least one source directory exists in config, and returns both the absolute base path and source-directory array. Runtime is O(s). Side effects are limited to config reads.
|
|
222
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
223
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
224
|
+
* @return {[string, string[]]} Tuple of absolute project base and configured source directories.
|
|
225
|
+
* @throws {ReqError} Throws when no source directories are configured.
|
|
226
|
+
*/
|
|
227
|
+
export function resolveProjectSrcDirs(projectBase: string, config?: UseReqConfig): [string, string[]] {
|
|
228
|
+
const base = resolveProjectBase(projectBase);
|
|
229
|
+
const effectiveConfig = config ?? loadConfig(base);
|
|
230
|
+
const srcDirs = effectiveConfig["src-dir"];
|
|
231
|
+
if (!Array.isArray(srcDirs) || srcDirs.length === 0) {
|
|
232
|
+
fail("Error: no source directories configured.", 1);
|
|
233
|
+
}
|
|
234
|
+
return [base, srcDirs];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @brief Loads project configuration and persists normalized path fields.
|
|
239
|
+
* @details Resolves the base path, normalizes persisted docs/tests/source directories into project-relative form, writes the normalized config back to disk, and returns the in-memory result without persisting runtime-derived path metadata. Runtime is dominated by config I/O. Side effects include config writes.
|
|
240
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
241
|
+
* @return {UseReqConfig} Normalized effective configuration.
|
|
242
|
+
* @satisfies CTN-012, REQ-146
|
|
243
|
+
*/
|
|
244
|
+
export function loadAndRepairConfig(projectBase: string): UseReqConfig {
|
|
245
|
+
const base = resolveProjectBase(projectBase);
|
|
246
|
+
const config = normalizeConfigPaths(base, loadConfig(base));
|
|
247
|
+
saveConfig(base, config);
|
|
248
|
+
return config;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @brief Counts tokens and characters for explicit files.
|
|
253
|
+
* @details Filters missing files into stderr warnings, counts metrics for valid files, and returns a formatted summary. Runtime is O(F + S). Side effects are limited to filesystem reads.
|
|
254
|
+
* @param[in] files {string[]} Explicit file paths.
|
|
255
|
+
* @return {ToolResult} Tool result containing the formatted summary and warnings.
|
|
256
|
+
* @throws {ReqError} Throws when no valid files are provided.
|
|
257
|
+
*/
|
|
258
|
+
export function runFilesTokens(files: string[]): ToolResult {
|
|
259
|
+
const validFiles: string[] = [];
|
|
260
|
+
const stderrLines: string[] = [];
|
|
261
|
+
files.forEach((filePath) => {
|
|
262
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
263
|
+
stderrLines.push(` Warning: skipping (not found): ${filePath}`);
|
|
264
|
+
} else {
|
|
265
|
+
validFiles.push(filePath);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (validFiles.length === 0) fail("Error: no valid files provided.", 1, "", stderrLines.join("\n"));
|
|
269
|
+
return ok(`${formatPackSummary(countFilesMetrics(validFiles))}\n`, stderrLines.join("\n"));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @brief Generates the structured references JSON payload for explicit files.
|
|
274
|
+
* @details Builds the agent-oriented references payload in caller order, preserves skipped and failed inputs as structured file records, emits deterministic JSON to stdout, and mirrors structured diagnostics to stderr. Runtime is O(F log F + S). Side effects are limited to filesystem reads and optional stderr logging.
|
|
275
|
+
* @param[in] files {string[]} Explicit file paths.
|
|
276
|
+
* @param[in] cwd {string} Base directory used for canonical path resolution. Defaults to `process.cwd()`.
|
|
277
|
+
* @param[in] verbose {boolean} When `true`, emit per-file progress diagnostics to stderr.
|
|
278
|
+
* @return {ToolResult} Successful tool result containing structured JSON.
|
|
279
|
+
* @satisfies REQ-011, REQ-076, REQ-077, REQ-078, REQ-079
|
|
280
|
+
*/
|
|
281
|
+
export function runFilesReferences(files: string[], cwd = process.cwd(), verbose = false): ToolResult {
|
|
282
|
+
const payload = buildReferenceToolPayload({
|
|
283
|
+
toolName: "files-references",
|
|
284
|
+
scope: "explicit-files",
|
|
285
|
+
baseDir: cwd,
|
|
286
|
+
requestedPaths: files,
|
|
287
|
+
verbose,
|
|
288
|
+
});
|
|
289
|
+
const stderr = buildReferenceToolExecutionStderr(payload);
|
|
290
|
+
if (payload.summary.processable_file_count === 0) {
|
|
291
|
+
fail("Error: no valid source files provided.", 1, "", stderr);
|
|
292
|
+
}
|
|
293
|
+
if (payload.summary.analyzed_file_count === 0) {
|
|
294
|
+
fail("Error: no valid source files processed.", 1, "", stderr);
|
|
295
|
+
}
|
|
296
|
+
return ok(`${JSON.stringify(payload, null, 2)}\n`, stderr);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @brief Compresses explicit files into compact source excerpts.
|
|
301
|
+
* @details Delegates to `compressFiles` using the caller working directory as the relative-output base by default. Runtime is O(F + S). Side effects are limited to filesystem reads and optional stderr logging.
|
|
302
|
+
* @param[in] files {string[]} Explicit file paths.
|
|
303
|
+
* @param[in] cwd {string} Base directory for relative output formatting. Defaults to `process.cwd()`.
|
|
304
|
+
* @param[in] enableLineNumbers {boolean} When `true`, preserve original source line numbers.
|
|
305
|
+
* @param[in] verbose {boolean} When `true`, emit per-file diagnostics to stderr.
|
|
306
|
+
* @return {ToolResult} Successful tool result containing compressed output.
|
|
307
|
+
*/
|
|
308
|
+
export function runFilesCompress(files: string[], cwd = process.cwd(), enableLineNumbers = false, verbose = false): ToolResult {
|
|
309
|
+
return ok(`${compressFiles(files, enableLineNumbers, verbose, cwd)}\n`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @brief Finds named constructs in explicit files.
|
|
314
|
+
* @details Expects `[TAG, PATTERN, ...FILES]`, validates minimum arity, and delegates to `findConstructsInFiles`. Runtime is O(F + S + M). Side effects are limited to filesystem reads and optional stderr logging.
|
|
315
|
+
* @param[in] argsList {string[]} Positional argument list containing tag filter, regex pattern, and files.
|
|
316
|
+
* @param[in] enableLineNumbers {boolean} When `true`, preserve original source line numbers in excerpts.
|
|
317
|
+
* @param[in] verbose {boolean} When `true`, emit diagnostics to stderr.
|
|
318
|
+
* @return {ToolResult} Successful tool result containing construct markdown.
|
|
319
|
+
* @throws {ReqError} Throws when required arguments are missing.
|
|
320
|
+
*/
|
|
321
|
+
export function runFilesFind(argsList: string[], enableLineNumbers = false, verbose = false): ToolResult {
|
|
322
|
+
if (argsList.length < 3) {
|
|
323
|
+
fail("Error: --files-find requires at least TAG, PATTERN, and one FILE.", 1);
|
|
324
|
+
}
|
|
325
|
+
const [tagFilter, pattern, ...files] = argsList;
|
|
326
|
+
return ok(`${findConstructsInFiles(files, tagFilter!, pattern!, enableLineNumbers, verbose)}\n`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @brief Generates the structured references JSON payload for configured source directories.
|
|
331
|
+
* @details Resolves the project base, collects configured source files, builds the agent-oriented references payload, emits deterministic JSON to stdout, and mirrors structured diagnostics to stderr. Runtime is O(F log F + S). Side effects are limited to filesystem reads and optional stderr logging.
|
|
332
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
333
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
334
|
+
* @param[in] verbose {boolean} When `true`, emit per-file diagnostics to stderr.
|
|
335
|
+
* @return {ToolResult} Successful tool result containing structured JSON.
|
|
336
|
+
* @throws {ReqError} Throws when no source files are found or no file can be analyzed.
|
|
337
|
+
* @satisfies REQ-014, REQ-076, REQ-077, REQ-078, REQ-079
|
|
338
|
+
*/
|
|
339
|
+
export function runReferences(projectBase: string, config?: UseReqConfig, verbose = false): ToolResult {
|
|
340
|
+
const [base, srcDirs] = resolveProjectSrcDirs(projectBase, config);
|
|
341
|
+
const files = collectSourceFiles(srcDirs, base);
|
|
342
|
+
if (files.length === 0) fail("Error: no source files found in configured directories.", 1);
|
|
343
|
+
const payload = buildReferenceToolPayload({
|
|
344
|
+
toolName: "references",
|
|
345
|
+
scope: "configured-source-directories",
|
|
346
|
+
baseDir: base,
|
|
347
|
+
requestedPaths: files,
|
|
348
|
+
sourceDirectoryPaths: srcDirs,
|
|
349
|
+
verbose,
|
|
350
|
+
});
|
|
351
|
+
const stderr = buildReferenceToolExecutionStderr(payload);
|
|
352
|
+
if (payload.summary.analyzed_file_count === 0) {
|
|
353
|
+
fail("Error: no valid source files processed.", 1, "", stderr);
|
|
354
|
+
}
|
|
355
|
+
return ok(`${JSON.stringify(payload, null, 2)}\n`, stderr);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @brief Compresses all source files from configured source directories.
|
|
360
|
+
* @details Resolves the project base, collects source files, and delegates to `compressFiles`. Runtime is O(F + S). Side effects are limited to filesystem reads and optional stderr logging.
|
|
361
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
362
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
363
|
+
* @param[in] enableLineNumbers {boolean} When `true`, preserve original source line numbers.
|
|
364
|
+
* @param[in] verbose {boolean} When `true`, emit per-file diagnostics to stderr.
|
|
365
|
+
* @return {ToolResult} Successful tool result containing compressed output.
|
|
366
|
+
* @throws {ReqError} Throws when no source files are found.
|
|
367
|
+
*/
|
|
368
|
+
export function runCompress(projectBase: string, config?: UseReqConfig, enableLineNumbers = false, verbose = false): ToolResult {
|
|
369
|
+
const [base, srcDirs] = resolveProjectSrcDirs(projectBase, config);
|
|
370
|
+
const files = collectSourceFiles(srcDirs, base);
|
|
371
|
+
if (files.length === 0) fail("Error: no source files found in configured directories.", 1);
|
|
372
|
+
return ok(`${compressFiles(files, enableLineNumbers, verbose, base)}\n`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @brief Finds named constructs across configured project source files.
|
|
377
|
+
* @details Resolves the project base, collects source files, delegates to `findConstructsInFiles`, and converts thrown search errors into structured `ReqError` failures. Runtime is O(F + S + M). Side effects are limited to filesystem reads and optional stderr logging.
|
|
378
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
379
|
+
* @param[in] tagFilter {string} Pipe-delimited tag filter.
|
|
380
|
+
* @param[in] pattern {string} Regular expression applied to construct names.
|
|
381
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
382
|
+
* @param[in] enableLineNumbers {boolean} When `true`, preserve original source line numbers in excerpts.
|
|
383
|
+
* @param[in] verbose {boolean} When `true`, emit diagnostics to stderr.
|
|
384
|
+
* @return {ToolResult} Successful tool result containing construct markdown.
|
|
385
|
+
* @throws {ReqError} Throws when no source files are found or the search fails.
|
|
386
|
+
*/
|
|
387
|
+
export function runFind(projectBase: string, tagFilter: string, pattern: string, config?: UseReqConfig, enableLineNumbers = false, verbose = false): ToolResult {
|
|
388
|
+
const [base, srcDirs] = resolveProjectSrcDirs(projectBase, config);
|
|
389
|
+
const files = collectSourceFiles(srcDirs, base);
|
|
390
|
+
if (files.length === 0) fail("Error: no source files found in configured directories.", 1);
|
|
391
|
+
try {
|
|
392
|
+
return ok(`${findConstructsInFiles(files, tagFilter, pattern, enableLineNumbers, verbose)}\n`);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
fail(error instanceof Error ? error.message : String(error), 1);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @brief Counts tokens for canonical documentation files.
|
|
400
|
+
* @details Loads the configured docs directory, selects `REQUIREMENTS.md`, `WORKFLOW.md`, and `REFERENCES.md` when present, and delegates to `runFilesTokens`. Runtime is O(F + S). Side effects are limited to filesystem reads.
|
|
401
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
402
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
403
|
+
* @return {ToolResult} Tool result containing documentation token metrics.
|
|
404
|
+
* @throws {ReqError} Throws when no canonical docs files exist.
|
|
405
|
+
*/
|
|
406
|
+
export function runTokens(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
407
|
+
const base = resolveProjectBase(projectBase);
|
|
408
|
+
const effectiveConfig = config ?? loadConfig(base);
|
|
409
|
+
const docsDir = effectiveConfig["docs-dir"].replace(/[/\\]+$/, "");
|
|
410
|
+
const docsPath = path.join(base, docsDir);
|
|
411
|
+
const canonicalNames = ["REQUIREMENTS.md", "WORKFLOW.md", "REFERENCES.md"];
|
|
412
|
+
const files = canonicalNames.map((name) => path.join(docsPath, name)).filter((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
413
|
+
if (files.length === 0) fail("Error: no canonical docs files found in --docs-dir.", 1);
|
|
414
|
+
return runFilesTokens(files);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @brief Runs configured static checks for explicit files.
|
|
419
|
+
* @details Loads the effective static-check config, groups checks by file extension language, captures checker stdout for each configured entry, and aggregates stderr warnings for invalid paths. Runtime is O(F * C) plus external checker cost. Side effects include filesystem reads, stdout interception, and process spawning.
|
|
420
|
+
* @param[in] files {string[]} Explicit file paths.
|
|
421
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
422
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
423
|
+
* @return {ToolResult} Aggregated static-check result.
|
|
424
|
+
*/
|
|
425
|
+
export function runFilesStaticCheck(files: string[], projectBase: string, config?: UseReqConfig): ToolResult {
|
|
426
|
+
const base = resolveProjectBase(projectBase);
|
|
427
|
+
const effectiveConfig = config ?? loadConfig(base);
|
|
428
|
+
const scConfig = effectiveConfig["static-check"] ?? {};
|
|
429
|
+
let stderr = "";
|
|
430
|
+
let overall = 0;
|
|
431
|
+
let stdout = "";
|
|
432
|
+
for (const rawPath of files) {
|
|
433
|
+
if (!fs.existsSync(rawPath) || !fs.statSync(rawPath).isFile()) {
|
|
434
|
+
stderr += `${stderr ? "\n" : ""} Warning: skipping (not found or not a file): ${rawPath}`;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const filePath = path.resolve(rawPath);
|
|
438
|
+
const lang = STATIC_CHECK_EXT_TO_LANG[path.extname(filePath).toLowerCase()];
|
|
439
|
+
if (!lang) continue;
|
|
440
|
+
const langConfigs = scConfig[lang] ?? [];
|
|
441
|
+
for (const langConfig of langConfigs) {
|
|
442
|
+
const previousWrite = process.stdout.write.bind(process.stdout);
|
|
443
|
+
let captured = "";
|
|
444
|
+
(process.stdout.write as unknown as (chunk: string | Uint8Array) => boolean) = ((chunk: string | Uint8Array) => {
|
|
445
|
+
captured += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
446
|
+
return true;
|
|
447
|
+
}) as unknown as typeof process.stdout.write;
|
|
448
|
+
try {
|
|
449
|
+
const rc = dispatchStaticCheckForFile(filePath, langConfig, {
|
|
450
|
+
failOnly: true,
|
|
451
|
+
projectBase: base,
|
|
452
|
+
});
|
|
453
|
+
if (rc !== 0) overall = 1;
|
|
454
|
+
} finally {
|
|
455
|
+
process.stdout.write = previousWrite;
|
|
456
|
+
}
|
|
457
|
+
stdout += captured;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { stdout, stderr, code: overall };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* @brief Runs configured static checks for project source and test directories.
|
|
465
|
+
* @details Collects source and test files, excludes fixture roots, and delegates to `runFilesStaticCheck`. Runtime is O(F * C) plus external checker cost. Side effects include filesystem reads, stdout interception, and process spawning.
|
|
466
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
467
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
468
|
+
* @return {ToolResult} Aggregated static-check result.
|
|
469
|
+
* @throws {ReqError} Throws when no source files are found.
|
|
470
|
+
*/
|
|
471
|
+
export function runProjectStaticCheck(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
472
|
+
const base = resolveProjectBase(projectBase);
|
|
473
|
+
const effectiveConfig = config ?? loadConfig(base);
|
|
474
|
+
const selectionDirs = [...effectiveConfig["src-dir"], effectiveConfig["tests-dir"]];
|
|
475
|
+
let files = collectSourceFiles(selectionDirs, base);
|
|
476
|
+
const testsDirRel = makeRelativeIfContainsProject(effectiveConfig["tests-dir"], base).split(path.sep).join("/").replace(/^\.?\/?/, "").replace(/\/+$/, "");
|
|
477
|
+
const fixtureRoots = new Set(["tests/fixtures", testsDirRel ? `${testsDirRel}/fixtures` : "fixtures"]);
|
|
478
|
+
files = files.filter((filePath) => {
|
|
479
|
+
const rel = path.relative(base, filePath).split(path.sep).join("/");
|
|
480
|
+
return ![...fixtureRoots].some((fixtureRoot) => rel === fixtureRoot || rel.startsWith(`${fixtureRoot}/`));
|
|
481
|
+
});
|
|
482
|
+
if (files.length === 0) fail("Error: no source files found in configured directories.", 1);
|
|
483
|
+
return runFilesStaticCheck(files, base, effectiveConfig);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @brief Verifies that the effective repository root is clean and has a valid HEAD.
|
|
488
|
+
* @details Resolves the runtime git root for the current execution path, checks work-tree status, rejects uncommitted changes, and verifies either a symbolic ref or detached HEAD hash exists. Runtime is dominated by git execution. Side effects include process spawning.
|
|
489
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
490
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
491
|
+
* @return {ToolResult} Successful empty result when the repository state is valid.
|
|
492
|
+
* @throws {ReqError} Throws when the runtime git root is unavailable or repository status is unclear.
|
|
493
|
+
* @satisfies REQ-145, REQ-146
|
|
494
|
+
*/
|
|
495
|
+
export function runGitCheck(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
496
|
+
const base = resolveProjectBase(projectBase);
|
|
497
|
+
void config;
|
|
498
|
+
const gitPath = resolveEffectiveGitPath(base);
|
|
499
|
+
if (!gitPath) {
|
|
500
|
+
fail("Error: git-path is unavailable for the current runtime path.", 11);
|
|
501
|
+
}
|
|
502
|
+
const insideResult = runCapture(["git", "rev-parse", "--is-inside-work-tree"], { cwd: gitPath });
|
|
503
|
+
if (insideResult.error || insideResult.status !== 0 || insideResult.stdout.trim() !== "true") {
|
|
504
|
+
fail("ERROR: Git status unclear!", 1);
|
|
505
|
+
}
|
|
506
|
+
const statusResult = runCapture(["git", "status", "--porcelain"], { cwd: gitPath });
|
|
507
|
+
if (statusResult.error || statusResult.status !== 0 || statusResult.stdout.trim() !== "") {
|
|
508
|
+
fail("ERROR: Git status unclear!", 1);
|
|
509
|
+
}
|
|
510
|
+
const symbolicHead = runCapture(["git", "symbolic-ref", "-q", "HEAD"], { cwd: gitPath });
|
|
511
|
+
if (symbolicHead.error || symbolicHead.status !== 0) {
|
|
512
|
+
const detachedHead = runCapture(["git", "rev-parse", "--verify", "HEAD"], { cwd: gitPath });
|
|
513
|
+
if (detachedHead.error || detachedHead.status !== 0) {
|
|
514
|
+
fail("ERROR: Git status unclear!", 1);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return ok();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* @brief Verifies that canonical documentation files exist.
|
|
522
|
+
* @details Checks the configured docs directory for `REQUIREMENTS.md`, `WORKFLOW.md`, and `REFERENCES.md`, and throws a guided error for the first missing file. Runtime is O(1) plus filesystem existence checks. Side effects are limited to filesystem reads.
|
|
523
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
524
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
525
|
+
* @return {ToolResult} Successful empty result when all canonical docs exist.
|
|
526
|
+
* @throws {ReqError} Throws when a required doc file is missing.
|
|
527
|
+
*/
|
|
528
|
+
export function runDocsCheck(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
529
|
+
const base = resolveProjectBase(projectBase);
|
|
530
|
+
const effectiveConfig = config ?? loadConfig(base);
|
|
531
|
+
const docsDir = effectiveConfig["docs-dir"].replace(/[/\\]+$/, "");
|
|
532
|
+
const docPath = path.join(base, docsDir);
|
|
533
|
+
for (const [filename, promptCmd] of [
|
|
534
|
+
["REQUIREMENTS.md", "/req-write"],
|
|
535
|
+
["WORKFLOW.md", "/req-workflow"],
|
|
536
|
+
["REFERENCES.md", "/req-references"],
|
|
537
|
+
] as const) {
|
|
538
|
+
const fullPath = path.join(docPath, filename);
|
|
539
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
|
|
540
|
+
const message = `ERROR: File ${docPath}/${filename} does not exist, generate it with the ${promptCmd} prompt!`;
|
|
541
|
+
fail(message, 1, `${message}\n`, message);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return ok();
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* @brief Generates the standardized worktree name for the effective repository root.
|
|
549
|
+
* @details Resolves the runtime git root constrained by the current base path, combines the repository basename, sanitized current branch, and a timestamp-based execution identifier into a deterministic `useReq-...` name. Runtime is O(1) plus git execution cost. Side effects include process spawning.
|
|
550
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
551
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
552
|
+
* @return {ToolResult} Successful result containing the generated worktree name and trailing newline.
|
|
553
|
+
* @throws {ReqError} Throws when the runtime git root is unavailable.
|
|
554
|
+
* @satisfies REQ-145, REQ-146
|
|
555
|
+
*/
|
|
556
|
+
export function runGitWtName(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
557
|
+
const base = resolveProjectBase(projectBase);
|
|
558
|
+
void config;
|
|
559
|
+
const gitPath = resolveEffectiveGitPath(base);
|
|
560
|
+
if (!gitPath) {
|
|
561
|
+
fail("Error: git-path is unavailable for the current runtime path.", 11);
|
|
562
|
+
}
|
|
563
|
+
const projectName = path.basename(gitPath);
|
|
564
|
+
const branchResult = runCapture(["git", "branch", "--show-current"], { cwd: gitPath });
|
|
565
|
+
const branch = branchResult.error ? "unknown" : branchResult.stdout.trim();
|
|
566
|
+
const sanitizedBranch = sanitizeBranchName(branch);
|
|
567
|
+
const now = new Date();
|
|
568
|
+
const executionId = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
|
|
569
|
+
return ok(`useReq-${projectName}-${sanitizedBranch}-${executionId}\n`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* @brief Tests whether a git worktree exists at an exact filesystem path.
|
|
574
|
+
* @details Parses `git worktree list --porcelain` output and compares normalized paths for exact equality. Runtime is O(n) in reported worktree count plus git execution cost. Side effects include process spawning.
|
|
575
|
+
* @param[in] gitPath {string} Git root used to query worktrees.
|
|
576
|
+
* @param[in] targetPath {string} Candidate worktree path.
|
|
577
|
+
* @return {boolean} `true` when a worktree exists at the exact target path.
|
|
578
|
+
* @throws {ReqError} Throws when the worktree list cannot be queried.
|
|
579
|
+
*/
|
|
580
|
+
function worktreePathExistsExact(gitPath: string, targetPath: string): boolean {
|
|
581
|
+
const result = runCapture(["git", "worktree", "list", "--porcelain"], { cwd: gitPath });
|
|
582
|
+
if (result.error || result.status !== 0) fail("Error: unable to query git worktree list.", 3);
|
|
583
|
+
const normalizedTarget = path.resolve(targetPath);
|
|
584
|
+
return result.stdout.split(/\r?\n/).some((line) => line.startsWith("worktree ") && path.resolve(line.slice("worktree ".length).trim()) === normalizedTarget);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* @brief Rolls back a partially created worktree and branch.
|
|
589
|
+
* @details Forces worktree removal and branch deletion, then throws if either rollback action fails. Runtime is dominated by git execution. Side effects include destructive git mutations.
|
|
590
|
+
* @param[in] gitPath {string} Git root path.
|
|
591
|
+
* @param[in] wtPath {string} Worktree path to remove.
|
|
592
|
+
* @param[in] wtName {string} Branch name to delete.
|
|
593
|
+
* @return {void} No return value.
|
|
594
|
+
* @throws {ReqError} Throws when rollback cannot be completed.
|
|
595
|
+
*/
|
|
596
|
+
function rollbackWorktreeCreate(gitPath: string, wtPath: string, wtName: string): void {
|
|
597
|
+
const removeResult = runCapture(["git", "worktree", "remove", wtPath, "--force"], { cwd: gitPath });
|
|
598
|
+
const branchResult = runCapture(["git", "branch", "-D", wtName], { cwd: gitPath });
|
|
599
|
+
if (removeResult.error || branchResult.error || removeResult.status !== 0 || branchResult.status !== 0) {
|
|
600
|
+
fail(`ERROR: Rollback failed for worktree or branch ${wtName}.`, 1);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* @brief Creates a dedicated git worktree and copies pi-usereq metadata into it.
|
|
606
|
+
* @details Validates the requested name, resolves base and git roots under the ancestor constraint, creates the worktree and branch, then mirrors the `.pi-usereq` directory into the corresponding path inside the new worktree. Runtime is dominated by git and filesystem operations. Side effects include worktree creation, branch creation, directory creation, and file copying.
|
|
607
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
608
|
+
* @param[in] wtName {string} Requested worktree and branch name.
|
|
609
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
610
|
+
* @return {ToolResult} Successful empty result when creation completes.
|
|
611
|
+
* @throws {ReqError} Throws for invalid names, missing git metadata, git failures, or copy finalization failures.
|
|
612
|
+
*/
|
|
613
|
+
export function runGitWtCreate(projectBase: string, wtName: string, config?: UseReqConfig): ToolResult {
|
|
614
|
+
if (!validateWtName(wtName)) {
|
|
615
|
+
const message = `ERROR: Invalid worktree/branch name: ${wtName}.`;
|
|
616
|
+
fail(message, 1, `${message}\n`, message);
|
|
617
|
+
}
|
|
618
|
+
const base = resolveProjectBase(projectBase);
|
|
619
|
+
void config;
|
|
620
|
+
const gitPath = resolveEffectiveGitPath(base);
|
|
621
|
+
const resolvedBasePath = base;
|
|
622
|
+
if (!gitPath) {
|
|
623
|
+
fail("Error: git-path is unavailable for the current runtime path.", 11);
|
|
624
|
+
}
|
|
625
|
+
const gitRoot = path.resolve(gitPath);
|
|
626
|
+
const parentPath = path.dirname(gitRoot);
|
|
627
|
+
const baseDir = path.relative(gitRoot, resolvedBasePath);
|
|
628
|
+
const wtDest = path.join(parentPath, wtName);
|
|
629
|
+
const addResult = runCapture(["git", "worktree", "add", wtDest, "-b", wtName], { cwd: gitRoot });
|
|
630
|
+
if (addResult.error || addResult.status !== 0) {
|
|
631
|
+
fail(`Error: git worktree add failed: ${addResult.stderr.trim()}`, 1);
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
const wtBaseDir = path.join(wtDest, baseDir);
|
|
635
|
+
const srcReq = path.join(resolvedBasePath, ".pi-usereq");
|
|
636
|
+
const dstReq = path.join(wtBaseDir, ".pi-usereq");
|
|
637
|
+
if (fs.existsSync(srcReq) && fs.statSync(srcReq).isDirectory() && !fs.existsSync(dstReq)) {
|
|
638
|
+
fs.mkdirSync(path.dirname(dstReq), { recursive: true });
|
|
639
|
+
fs.cpSync(srcReq, dstReq, { recursive: true });
|
|
640
|
+
}
|
|
641
|
+
} catch {
|
|
642
|
+
rollbackWorktreeCreate(gitRoot, wtDest, wtName);
|
|
643
|
+
fail(`ERROR: Unable to finalize worktree creation for ${wtName}.`, 1);
|
|
644
|
+
}
|
|
645
|
+
return ok();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* @brief Deletes a dedicated git worktree and its branch.
|
|
650
|
+
* @details Verifies that either the worktree path or branch exists, removes the worktree when present, deletes the branch when present, and fails atomically when either delete step reports an error. Runtime is dominated by git execution. Side effects include destructive git mutations.
|
|
651
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
652
|
+
* @param[in] wtName {string} Exact worktree and branch name.
|
|
653
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
654
|
+
* @return {ToolResult} Successful empty result when deletion completes.
|
|
655
|
+
* @throws {ReqError} Throws when git metadata is missing, the target does not exist, or removal fails.
|
|
656
|
+
*/
|
|
657
|
+
export function runGitWtDelete(projectBase: string, wtName: string, config?: UseReqConfig): ToolResult {
|
|
658
|
+
const base = resolveProjectBase(projectBase);
|
|
659
|
+
void config;
|
|
660
|
+
const gitPath = resolveEffectiveGitPath(base);
|
|
661
|
+
if (!gitPath) {
|
|
662
|
+
fail("Error: git-path is unavailable for the current runtime path.", 11);
|
|
663
|
+
}
|
|
664
|
+
const gitRoot = path.resolve(gitPath);
|
|
665
|
+
const parentPath = path.dirname(gitRoot);
|
|
666
|
+
const wtPath = path.join(parentPath, wtName);
|
|
667
|
+
const branchExists = (() => {
|
|
668
|
+
const result = runCapture(["git", "show-ref", "--verify", `refs/heads/${wtName}`], { cwd: gitRoot });
|
|
669
|
+
return result.status === 0;
|
|
670
|
+
})();
|
|
671
|
+
const wtExists = worktreePathExistsExact(gitRoot, wtPath);
|
|
672
|
+
if (!branchExists && !wtExists) {
|
|
673
|
+
const message = `ERROR: Invalid worktree or branch name: ${wtName}.`;
|
|
674
|
+
fail(message, 1, `${message}\n`, message);
|
|
675
|
+
}
|
|
676
|
+
let errorOccurred = false;
|
|
677
|
+
if (wtExists) {
|
|
678
|
+
const result = runCapture(["git", "worktree", "remove", wtPath, "--force"], { cwd: base });
|
|
679
|
+
errorOccurred ||= !!result.error || result.status !== 0;
|
|
680
|
+
}
|
|
681
|
+
if (branchExists) {
|
|
682
|
+
const result = runCapture(["git", "branch", "-D", wtName], { cwd: base });
|
|
683
|
+
errorOccurred ||= !!result.error || result.status !== 0;
|
|
684
|
+
}
|
|
685
|
+
if (errorOccurred) {
|
|
686
|
+
const message = `ERROR: Unable to remove worktree or branch ${wtName}.`;
|
|
687
|
+
fail(message, 1, `${message}\n`, message);
|
|
688
|
+
}
|
|
689
|
+
return ok();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @brief Returns the effective git root path for the current execution context.
|
|
694
|
+
* @details Resolves the git root constrained by the current base path, formats it with the runtime path display serializer, and writes the resulting path followed by a newline. Runtime is O(p) plus config-load and optional git-probing cost. Side effects are limited to filesystem reads and git subprocess execution.
|
|
695
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
696
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
697
|
+
* @return {ToolResult} Successful result containing the derived git path or an empty line.
|
|
698
|
+
*/
|
|
699
|
+
export function runGitPath(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
700
|
+
const base = resolveProjectBase(projectBase);
|
|
701
|
+
void config;
|
|
702
|
+
const gitPath = resolveEffectiveGitPath(base);
|
|
703
|
+
return ok(`${gitPath ? formatRuntimePathForDisplay(gitPath) : ""}\n`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* @brief Returns the current base path for the execution context.
|
|
708
|
+
* @details Resolves the current execution path, formats it with the runtime path display serializer, and writes the resulting base path followed by a newline. Runtime is O(p). Side effects are limited to filesystem reads.
|
|
709
|
+
* @param[in] projectBase {string} Candidate project root.
|
|
710
|
+
* @param[in] config {UseReqConfig | undefined} Optional preloaded configuration.
|
|
711
|
+
* @return {ToolResult} Successful result containing the base path and trailing newline.
|
|
712
|
+
*/
|
|
713
|
+
export function runGetBasePath(projectBase: string, config?: UseReqConfig): ToolResult {
|
|
714
|
+
const base = resolveProjectBase(projectBase);
|
|
715
|
+
void config;
|
|
716
|
+
return ok(`${formatRuntimePathForDisplay(base)}\n`);
|
|
717
|
+
}
|