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,729 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* @brief Provides token, size, and structure counting utilities for agent-oriented file payloads.
|
|
4
|
+
* @details Wraps `js-tiktoken` encoding lookup, extracts per-file structural facts, and builds machine-oriented JSON payloads for token-centric tools. Runtime is linear in processed text size plus sort cost for derived ordering hints. Side effects are limited to filesystem reads in file-based helpers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { getEncoding } from "js-tiktoken";
|
|
10
|
+
import { detectLanguage as detectSourceLanguage } from "./compress.js";
|
|
11
|
+
import { parseDoxygenComment, type DoxygenFieldMap } from "./doxygen-parser.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @brief Declares the tokenizer encoding used by token-count workflows.
|
|
15
|
+
* @details Stores the canonical `js-tiktoken` encoding identifier reused by CLI and agent-tool token metrics. Access complexity is O(1). No side effects occur.
|
|
16
|
+
*/
|
|
17
|
+
export const TOKEN_COUNTER_ENCODING = "cl100k_base";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @brief Enumerates supported token-payload scopes.
|
|
21
|
+
* @details Distinguishes explicit-file requests from canonical-document requests while preserving one stable JSON contract. The alias is compile-time only and introduces no runtime cost.
|
|
22
|
+
*/
|
|
23
|
+
export type TokenToolScope = "explicit-files" | "canonical-docs";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @brief Enumerates supported per-file token-entry statuses.
|
|
27
|
+
* @details Separates counted files, read-time failures, and skipped inputs so downstream agents can branch without reparsing text diagnostics. The alias is compile-time only and introduces no runtime cost.
|
|
28
|
+
*/
|
|
29
|
+
export type TokenFileStatus = "counted" | "error" | "skipped";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @brief Describes one per-file token metric record.
|
|
33
|
+
* @details Stores canonical file identity plus numeric token, character, byte, and line metrics extracted from one readable file. Optional metadata remains isolated in dedicated fields so agents can access headings and Doxygen fields without reparsing monolithic text. The interface is compile-time only and introduces no runtime cost.
|
|
34
|
+
*/
|
|
35
|
+
export interface CountFileMetricsResult {
|
|
36
|
+
file: string;
|
|
37
|
+
tokens: number;
|
|
38
|
+
chars: number;
|
|
39
|
+
bytes: number;
|
|
40
|
+
lines: number;
|
|
41
|
+
startLineNumber: number;
|
|
42
|
+
endLineNumber: number;
|
|
43
|
+
fileExtension: string;
|
|
44
|
+
languageName?: string;
|
|
45
|
+
primaryHeadingText?: string;
|
|
46
|
+
doxygenFileFields?: DoxygenFieldMap;
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @brief Describes one file entry in the agent-oriented token payload.
|
|
52
|
+
* @details Orders direct-access path identifiers before source facts and numeric metrics so agents can branch without reparsing formatted strings. Optional metadata captures markdown headings or Doxygen file fields when they can be derived from the source content. The interface is compile-time only and introduces no runtime cost.
|
|
53
|
+
*/
|
|
54
|
+
export interface TokenToolFileEntry {
|
|
55
|
+
request_index: number;
|
|
56
|
+
input_path: string;
|
|
57
|
+
canonical_path: string;
|
|
58
|
+
absolute_path: string;
|
|
59
|
+
file_name: string;
|
|
60
|
+
file_extension: string;
|
|
61
|
+
language_name?: string;
|
|
62
|
+
status: TokenFileStatus;
|
|
63
|
+
exists: boolean;
|
|
64
|
+
is_file: boolean;
|
|
65
|
+
start_line_number: number;
|
|
66
|
+
end_line_number: number;
|
|
67
|
+
line_count: number;
|
|
68
|
+
primary_heading_text?: string;
|
|
69
|
+
doxygen_file_fields?: DoxygenFieldMap;
|
|
70
|
+
token_count: number;
|
|
71
|
+
character_count: number;
|
|
72
|
+
byte_count: number;
|
|
73
|
+
token_share: number;
|
|
74
|
+
character_share: number;
|
|
75
|
+
byte_share: number;
|
|
76
|
+
error_message?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @brief Describes the request section of the agent-oriented token payload.
|
|
81
|
+
* @details Captures tool identity, scope, path-resolution base, encoding, and requested path inventories so agents can reason about how metrics were selected. The interface is compile-time only and introduces no runtime cost.
|
|
82
|
+
*/
|
|
83
|
+
export interface TokenToolRequestSection {
|
|
84
|
+
tool_name: string;
|
|
85
|
+
scope: TokenToolScope;
|
|
86
|
+
encoding_name: string;
|
|
87
|
+
base_dir_path: string;
|
|
88
|
+
requested_file_count: number;
|
|
89
|
+
requested_input_paths: string[];
|
|
90
|
+
requested_canonical_paths: string[];
|
|
91
|
+
docs_dir_path?: string;
|
|
92
|
+
canonical_doc_names?: string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @brief Describes the summary section of the agent-oriented token payload.
|
|
97
|
+
* @details Exposes aggregate counts, sizes, totals, and per-file averages as numeric fields with explicit units so agents can branch on totals without reparsing formatted strings. The interface is compile-time only and introduces no runtime cost.
|
|
98
|
+
*/
|
|
99
|
+
export interface TokenToolSummarySection {
|
|
100
|
+
processable_file_count: number;
|
|
101
|
+
counted_file_count: number;
|
|
102
|
+
error_file_count: number;
|
|
103
|
+
skipped_file_count: number;
|
|
104
|
+
total_token_count: number;
|
|
105
|
+
total_character_count: number;
|
|
106
|
+
total_byte_count: number;
|
|
107
|
+
total_line_count: number;
|
|
108
|
+
average_token_count_per_counted_file: number;
|
|
109
|
+
average_character_count_per_counted_file: number;
|
|
110
|
+
average_byte_count_per_counted_file: number;
|
|
111
|
+
average_line_count_per_counted_file: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @brief Describes one skipped-input or read-error observation.
|
|
116
|
+
* @details Preserves both the caller-provided path and the canonicalized path plus a stable machine-readable reason string. The interface is compile-time only and introduces no runtime cost.
|
|
117
|
+
*/
|
|
118
|
+
export interface TokenToolPathIssue {
|
|
119
|
+
input_path: string;
|
|
120
|
+
canonical_path: string;
|
|
121
|
+
reason: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @brief Describes the dominant counted file for one metric ordering.
|
|
126
|
+
* @details Exposes the canonical path plus the numeric token metrics that justify why the file dominates the current context budget. The interface is compile-time only and introduces no runtime cost.
|
|
127
|
+
*/
|
|
128
|
+
export interface TokenToolDominantFileObservation {
|
|
129
|
+
canonical_path: string;
|
|
130
|
+
token_count: number;
|
|
131
|
+
token_share: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @brief Describes the source-observation subsection of the guidance payload.
|
|
136
|
+
* @details Separates measured ordering facts and path issues from derived recommendations so agents can reason about raw observations independently. The interface is compile-time only and introduces no runtime cost.
|
|
137
|
+
*/
|
|
138
|
+
export interface TokenToolSourceObservationsSection {
|
|
139
|
+
counted_paths_by_token_count_desc: string[];
|
|
140
|
+
counted_paths_by_line_count_desc: string[];
|
|
141
|
+
skipped_inputs: TokenToolPathIssue[];
|
|
142
|
+
error_inputs: TokenToolPathIssue[];
|
|
143
|
+
dominant_token_file?: TokenToolDominantFileObservation;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @brief Describes one derived recommendation in the guidance payload.
|
|
148
|
+
* @details Provides a stable recommendation kind, the basis metric used to derive it, and the ordered path list the agent can follow directly. The interface is compile-time only and introduces no runtime cost.
|
|
149
|
+
*/
|
|
150
|
+
export interface TokenToolRecommendation {
|
|
151
|
+
kind: string;
|
|
152
|
+
basis_metric_name: "token_count" | "line_count";
|
|
153
|
+
ordered_paths: string[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @brief Describes one actionable next-step hint in the guidance payload.
|
|
158
|
+
* @details Supplies a stable hint kind, a focused ordered path subset, and the goal the agent can apply without reparsing surrounding prose. The interface is compile-time only and introduces no runtime cost.
|
|
159
|
+
*/
|
|
160
|
+
export interface TokenToolNextStepHint {
|
|
161
|
+
kind: string;
|
|
162
|
+
ordered_paths: string[];
|
|
163
|
+
goal: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @brief Describes the guidance section of the agent-oriented token payload.
|
|
168
|
+
* @details Separates source observations, derived recommendations, and actionable next-step hints so downstream agents can choose between raw evidence and planning heuristics without reparsing mixed prose. The interface is compile-time only and introduces no runtime cost.
|
|
169
|
+
*/
|
|
170
|
+
export interface TokenToolGuidanceSection {
|
|
171
|
+
source_observations: TokenToolSourceObservationsSection;
|
|
172
|
+
derived_recommendations: TokenToolRecommendation[];
|
|
173
|
+
actionable_next_steps: TokenToolNextStepHint[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @brief Describes the full agent-oriented token payload.
|
|
178
|
+
* @details Orders the top-level sections as request, summary, files, and guidance for deterministic downstream traversal. The interface is compile-time only and introduces no runtime cost.
|
|
179
|
+
*/
|
|
180
|
+
export interface TokenToolPayload {
|
|
181
|
+
request: TokenToolRequestSection;
|
|
182
|
+
summary: TokenToolSummarySection;
|
|
183
|
+
files: TokenToolFileEntry[];
|
|
184
|
+
guidance: TokenToolGuidanceSection;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @brief Describes the options required to build one agent-oriented token payload.
|
|
189
|
+
* @details Supplies tool identity, scope, path base, requested paths, and optional canonical-doc metadata while keeping counting behavior configurable through a stable object contract. The interface is compile-time only and introduces no runtime cost.
|
|
190
|
+
*/
|
|
191
|
+
export interface BuildTokenToolPayloadOptions {
|
|
192
|
+
toolName: string;
|
|
193
|
+
scope: TokenToolScope;
|
|
194
|
+
baseDir: string;
|
|
195
|
+
requestedPaths: string[];
|
|
196
|
+
docsDir?: string;
|
|
197
|
+
canonicalDocNames?: string[];
|
|
198
|
+
encodingName?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @brief Encapsulates one tokenizer instance for repeated token counting.
|
|
203
|
+
* @details Caches a `js-tiktoken` encoding object so multiple documents can be counted without repeated encoding lookup. Counting cost is O(n) in content length. The class mutates only instance state during construction.
|
|
204
|
+
*/
|
|
205
|
+
export class TokenCounter {
|
|
206
|
+
/**
|
|
207
|
+
* @brief Stores the tokenizer implementation used for subsequent counts.
|
|
208
|
+
* @details The field holds the encoder returned by `getEncoding`. Access complexity is O(1). The value is initialized once per instance.
|
|
209
|
+
*/
|
|
210
|
+
private encoding;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @brief Initializes a token counter for one encoding family.
|
|
214
|
+
* @details Resolves the named tokenizer once and reuses it across `countTokens` calls. Construction complexity is O(1) relative to caller-controlled input size. Side effects are limited to instance initialization.
|
|
215
|
+
* @param[in] encodingName {string} `js-tiktoken` encoding identifier. Defaults to `cl100k_base`.
|
|
216
|
+
* @return {TokenCounter} New token counter instance.
|
|
217
|
+
*/
|
|
218
|
+
constructor(encodingName = TOKEN_COUNTER_ENCODING) {
|
|
219
|
+
this.encoding = getEncoding(encodingName);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @brief Counts tokenizer tokens for one content string.
|
|
224
|
+
* @details Encodes the provided text with the cached tokenizer and returns the token sequence length. Returns `0` when encoding throws, which prevents counting failures from aborting higher-level workflows. Time complexity is O(n). No external state is mutated.
|
|
225
|
+
* @param[in] content {string} Text to tokenize.
|
|
226
|
+
* @return {number} Token count, or `0` when encoding fails.
|
|
227
|
+
*/
|
|
228
|
+
countTokens(content: string): number {
|
|
229
|
+
try {
|
|
230
|
+
return this.encoding.encode(content).length;
|
|
231
|
+
} catch {
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @brief Counts UTF-16 code-unit characters for one string.
|
|
238
|
+
* @details Returns JavaScript string length directly without normalization. Time complexity is O(1) under engine-maintained length metadata. No side effects occur.
|
|
239
|
+
* @param[in] content {string} Text to measure.
|
|
240
|
+
* @return {number} Raw character count.
|
|
241
|
+
*/
|
|
242
|
+
static countChars(content: string): number {
|
|
243
|
+
return content.length;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @brief Converts one filesystem path into the canonical token-payload path form.
|
|
249
|
+
* @details Emits a slash-normalized relative path when the target is under the supplied base directory; otherwise emits a slash-normalized absolute path. Runtime is O(p) in path length. No external state is mutated.
|
|
250
|
+
* @param[in] filePath {string} Candidate absolute or relative filesystem path.
|
|
251
|
+
* @param[in] baseDir {string} Reference directory used for relative canonicalization.
|
|
252
|
+
* @return {string} Canonicalized path string.
|
|
253
|
+
*/
|
|
254
|
+
function canonicalizeTokenPath(filePath: string, baseDir: string): string {
|
|
255
|
+
const absolutePath = path.resolve(filePath);
|
|
256
|
+
const absoluteBaseDir = path.resolve(baseDir);
|
|
257
|
+
const relativePath = path.relative(absoluteBaseDir, absolutePath).split(path.sep).join("/");
|
|
258
|
+
if (relativePath !== "" && !relativePath.startsWith("../") && relativePath !== "..") {
|
|
259
|
+
return relativePath;
|
|
260
|
+
}
|
|
261
|
+
return absolutePath.split(path.sep).join("/");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @brief Counts logical lines in one text payload.
|
|
266
|
+
* @details Counts newline separators while treating a trailing newline as line termination instead of an extra empty logical line. Runtime is O(n) in text length. No side effects occur.
|
|
267
|
+
* @param[in] content {string} Text payload.
|
|
268
|
+
* @return {number} Logical line count; `0` for empty content.
|
|
269
|
+
*/
|
|
270
|
+
function countLines(content: string): number {
|
|
271
|
+
if (content.length === 0) {
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
const newlineCount = content.match(/\r\n|\r|\n/g)?.length ?? 0;
|
|
275
|
+
return /(?:\r\n|\r|\n)$/.test(content) ? newlineCount : newlineCount + 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* @brief Strips YAML front matter from markdown content before heading extraction.
|
|
280
|
+
* @details Removes the first `--- ... ---` block only when it appears at the file start so heading detection can operate on semantic markdown content instead of metadata. Runtime is O(n) in content length. No side effects occur.
|
|
281
|
+
* @param[in] content {string} Markdown payload.
|
|
282
|
+
* @return {string} Markdown body without the leading front matter block.
|
|
283
|
+
*/
|
|
284
|
+
function stripMarkdownFrontMatter(content: string): string {
|
|
285
|
+
const frontMatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?(?:\n|$)/);
|
|
286
|
+
return frontMatterMatch ? content.slice(frontMatterMatch[0].length) : content;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @brief Extracts the first level-one markdown heading when present.
|
|
291
|
+
* @details Restricts extraction to markdown-like files, skips YAML front matter, and returns the first `# ` heading payload without surrounding whitespace. Runtime is O(n) in content length. No side effects occur.
|
|
292
|
+
* @param[in] content {string} File content.
|
|
293
|
+
* @param[in] filePath {string} Source path used for extension-based markdown detection.
|
|
294
|
+
* @return {string | undefined} First heading text, or `undefined` when absent or the file is not markdown-like.
|
|
295
|
+
*/
|
|
296
|
+
function extractPrimaryHeadingText(content: string, filePath: string): string | undefined {
|
|
297
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
298
|
+
if (![".md", ".markdown", ".mdx"].includes(extension)) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const headingMatch = stripMarkdownFrontMatter(content).match(/^#\s+(.+?)\s*$/m);
|
|
302
|
+
return headingMatch?.[1]?.trim() || undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @brief Infers a file language label optimized for agent payloads.
|
|
307
|
+
* @details Reuses source-language detection when available, normalizes markdown extensions explicitly, and falls back to the lowercase extension name without the leading dot. Runtime is O(1). No side effects occur.
|
|
308
|
+
* @param[in] filePath {string} File path whose extension should be classified.
|
|
309
|
+
* @return {string | undefined} Normalized language label, or `undefined` when the path has no usable extension.
|
|
310
|
+
*/
|
|
311
|
+
function inferLanguageName(filePath: string): string | undefined {
|
|
312
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
313
|
+
if ([".md", ".markdown"].includes(extension)) {
|
|
314
|
+
return "markdown";
|
|
315
|
+
}
|
|
316
|
+
if (extension === ".mdx") {
|
|
317
|
+
return "mdx";
|
|
318
|
+
}
|
|
319
|
+
return detectSourceLanguage(filePath) ?? (extension ? extension.slice(1) : undefined);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @brief Extracts leading Doxygen file fields when present.
|
|
324
|
+
* @details Tests common leading-comment syntaxes, normalizes an optional shebang away before matching, and returns the first non-empty parsed Doxygen map. Runtime is O(n) in comment length. No side effects occur.
|
|
325
|
+
* @param[in] content {string} File content.
|
|
326
|
+
* @return {DoxygenFieldMap | undefined} Parsed Doxygen field map, or `undefined` when no supported file-level fields are present.
|
|
327
|
+
*/
|
|
328
|
+
function extractLeadingDoxygenFields(content: string): DoxygenFieldMap | undefined {
|
|
329
|
+
const normalizedContent = content.replace(/^#![^\n]*\n/, "");
|
|
330
|
+
const candidates = [
|
|
331
|
+
normalizedContent.match(/^\s*(\/\*\*[\s\S]*?\*\/|\/\*![\s\S]*?\*\/)/)?.[1],
|
|
332
|
+
normalizedContent.match(/^(?:\s*\/\/\/?!?[^\n]*(?:\n|$))+/)?.[0],
|
|
333
|
+
normalizedContent.match(/^(?:\s*#(?:\s|$)[^\n]*(?:\n|$))+/)?.[0],
|
|
334
|
+
normalizedContent.match(/^\s*(?:"""[\s\S]*?"""|'''[\s\S]*?''')/)?.[0],
|
|
335
|
+
];
|
|
336
|
+
for (const candidate of candidates) {
|
|
337
|
+
if (!candidate) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const fields = parseDoxygenComment(candidate);
|
|
341
|
+
if (Object.keys(fields).length > 0) {
|
|
342
|
+
return fields;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @brief Rounds one ratio to six decimal places.
|
|
350
|
+
* @details Preserves zero exactly and otherwise limits floating-point noise so share fields remain stable across executions. Runtime is O(1). No side effects occur.
|
|
351
|
+
* @param[in] numerator {number} Partial numeric value.
|
|
352
|
+
* @param[in] denominator {number} Total numeric value.
|
|
353
|
+
* @return {number} Rounded ratio in range `[0, 1]` when the denominator is positive; `0` otherwise.
|
|
354
|
+
*/
|
|
355
|
+
function roundRatio(numerator: number, denominator: number): number {
|
|
356
|
+
if (denominator <= 0 || numerator <= 0) {
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
return Number((numerator / denominator).toFixed(6));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* @brief Orders canonical file paths by one numeric metric while removing duplicates.
|
|
364
|
+
* @details Filters to counted file entries, sorts by the supplied metric direction, breaks ties by canonical path, and preserves only the first occurrence of each path. Runtime is O(n log n). No external state is mutated.
|
|
365
|
+
* @param[in] files {TokenToolFileEntry[]} Token payload file entries.
|
|
366
|
+
* @param[in] metric {(entry: TokenToolFileEntry) => number} Numeric metric selector.
|
|
367
|
+
* @param[in] direction {"asc" | "desc"} Sort direction.
|
|
368
|
+
* @return {string[]} Unique canonical paths ordered by the requested metric.
|
|
369
|
+
*/
|
|
370
|
+
function orderPathsByMetric(
|
|
371
|
+
files: TokenToolFileEntry[],
|
|
372
|
+
metric: (entry: TokenToolFileEntry) => number,
|
|
373
|
+
direction: "asc" | "desc",
|
|
374
|
+
): string[] {
|
|
375
|
+
const sorted = files
|
|
376
|
+
.filter((entry) => entry.status === "counted")
|
|
377
|
+
.sort((left, right) => {
|
|
378
|
+
const leftMetric = metric(left);
|
|
379
|
+
const rightMetric = metric(right);
|
|
380
|
+
if (leftMetric === rightMetric) {
|
|
381
|
+
return left.canonical_path.localeCompare(right.canonical_path);
|
|
382
|
+
}
|
|
383
|
+
return direction === "desc" ? rightMetric - leftMetric : leftMetric - rightMetric;
|
|
384
|
+
});
|
|
385
|
+
const seen = new Set<string>();
|
|
386
|
+
const orderedPaths: string[] = [];
|
|
387
|
+
for (const entry of sorted) {
|
|
388
|
+
if (seen.has(entry.canonical_path)) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
seen.add(entry.canonical_path);
|
|
392
|
+
orderedPaths.push(entry.canonical_path);
|
|
393
|
+
}
|
|
394
|
+
return orderedPaths;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* @brief Probes one requested path before token counting.
|
|
399
|
+
* @details Resolves whether the target exists and is a regular file while capturing a stable skip reason for missing or non-file inputs. Runtime is dominated by one filesystem stat. Side effects are limited to filesystem reads.
|
|
400
|
+
* @param[in] absolutePath {string} Absolute path to inspect.
|
|
401
|
+
* @return {{ exists: boolean; isFile: boolean; reason?: string }} Path probe result.
|
|
402
|
+
*/
|
|
403
|
+
function probeRequestedPath(absolutePath: string): { exists: boolean; isFile: boolean; reason?: string } {
|
|
404
|
+
if (!fs.existsSync(absolutePath)) {
|
|
405
|
+
return { exists: false, isFile: false, reason: "not found" };
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
const isFile = fs.statSync(absolutePath).isFile();
|
|
409
|
+
return isFile ? { exists: true, isFile: true } : { exists: true, isFile: false, reason: "not a file" };
|
|
410
|
+
} catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
exists: true,
|
|
413
|
+
isFile: false,
|
|
414
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @brief Builds one rich per-file metrics record from readable content.
|
|
421
|
+
* @details Combines token, character, byte, and line counts with file-extension, inferred-language, heading, and Doxygen metadata extraction so agents can consume direct-access facts without reparsing the raw file. Runtime is O(n) in content length. No external state is mutated.
|
|
422
|
+
* @param[in] filePath {string} Absolute or project-local file path.
|
|
423
|
+
* @param[in] content {string} UTF-8 file content.
|
|
424
|
+
* @param[in] counter {TokenCounter} Reused token counter instance.
|
|
425
|
+
* @return {CountFileMetricsResult} Structured per-file metrics record.
|
|
426
|
+
*/
|
|
427
|
+
function buildCountFileMetricsResult(filePath: string, content: string, counter: TokenCounter): CountFileMetricsResult {
|
|
428
|
+
const lineCount = countLines(content);
|
|
429
|
+
return {
|
|
430
|
+
file: filePath,
|
|
431
|
+
tokens: counter.countTokens(content),
|
|
432
|
+
chars: TokenCounter.countChars(content),
|
|
433
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
434
|
+
lines: lineCount,
|
|
435
|
+
startLineNumber: lineCount > 0 ? 1 : 0,
|
|
436
|
+
endLineNumber: lineCount,
|
|
437
|
+
fileExtension: path.extname(filePath).toLowerCase(),
|
|
438
|
+
languageName: inferLanguageName(filePath),
|
|
439
|
+
primaryHeadingText: extractPrimaryHeadingText(content, filePath),
|
|
440
|
+
doxygenFileFields: extractLeadingDoxygenFields(content),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @brief Counts tokens, characters, bytes, and lines for one in-memory content string.
|
|
446
|
+
* @details Instantiates a `TokenCounter`, tokenizes the supplied text, and pairs the result with raw character length, UTF-8 byte size, and logical line count. Runtime is O(n). No filesystem I/O occurs.
|
|
447
|
+
* @param[in] content {string} Text payload to measure.
|
|
448
|
+
* @param[in] encodingName {string} Tokenizer identifier. Defaults to `cl100k_base`.
|
|
449
|
+
* @return {{ tokens: number; chars: number; bytes: number; lines: number }} Aggregate metrics for the supplied content.
|
|
450
|
+
*/
|
|
451
|
+
export function countFileMetrics(content: string, encodingName = TOKEN_COUNTER_ENCODING): {
|
|
452
|
+
tokens: number;
|
|
453
|
+
chars: number;
|
|
454
|
+
bytes: number;
|
|
455
|
+
lines: number;
|
|
456
|
+
} {
|
|
457
|
+
const counter = new TokenCounter(encodingName);
|
|
458
|
+
return {
|
|
459
|
+
tokens: counter.countTokens(content),
|
|
460
|
+
chars: TokenCounter.countChars(content),
|
|
461
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
462
|
+
lines: countLines(content),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @brief Counts tokens, characters, bytes, and lines for multiple files.
|
|
468
|
+
* @details Reuses a single `TokenCounter`, reads each file as UTF-8, and returns per-file metrics plus direct-access metadata such as heading and Doxygen file fields. Read failures are captured as error strings instead of aborting the entire batch. Runtime is O(F + S). Side effects are limited to filesystem reads.
|
|
469
|
+
* @param[in] filePaths {string[]} File paths to measure.
|
|
470
|
+
* @param[in] encodingName {string} Tokenizer identifier. Defaults to `cl100k_base`.
|
|
471
|
+
* @return {CountFileMetricsResult[]} Per-file metrics and optional read errors.
|
|
472
|
+
* @satisfies REQ-010, REQ-070, REQ-073
|
|
473
|
+
*/
|
|
474
|
+
export function countFilesMetrics(filePaths: string[], encodingName = TOKEN_COUNTER_ENCODING): CountFileMetricsResult[] {
|
|
475
|
+
const counter = new TokenCounter(encodingName);
|
|
476
|
+
return filePaths.map((filePath) => {
|
|
477
|
+
try {
|
|
478
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
479
|
+
return buildCountFileMetricsResult(filePath, content, counter);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return {
|
|
482
|
+
file: filePath,
|
|
483
|
+
tokens: 0,
|
|
484
|
+
chars: 0,
|
|
485
|
+
bytes: 0,
|
|
486
|
+
lines: 0,
|
|
487
|
+
startLineNumber: 0,
|
|
488
|
+
endLineNumber: 0,
|
|
489
|
+
fileExtension: path.extname(filePath).toLowerCase(),
|
|
490
|
+
languageName: inferLanguageName(filePath),
|
|
491
|
+
error: error instanceof Error ? error.message : String(error),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @brief Builds the agent-oriented JSON payload for token-centric tools.
|
|
499
|
+
* @details Validates requested paths against the filesystem, counts token metrics for processable files, preserves caller order in the file table, separates raw observations from derived guidance, and emits direct-access file facts such as line ranges, sizes, headings, and optional Doxygen file fields. Runtime is O(F log F + S). Side effects are limited to filesystem reads.
|
|
500
|
+
* @param[in] options {BuildTokenToolPayloadOptions} Payload-construction options.
|
|
501
|
+
* @return {TokenToolPayload} Structured token payload ordered as request, summary, files, guidance.
|
|
502
|
+
* @satisfies REQ-010, REQ-017, REQ-069, REQ-070, REQ-071, REQ-073, REQ-074, REQ-075
|
|
503
|
+
*/
|
|
504
|
+
export function buildTokenToolPayload(options: BuildTokenToolPayloadOptions): TokenToolPayload {
|
|
505
|
+
const baseDir = path.resolve(options.baseDir);
|
|
506
|
+
const encodingName = options.encodingName ?? TOKEN_COUNTER_ENCODING;
|
|
507
|
+
const requestedEntries = options.requestedPaths.map((inputPath, index) => {
|
|
508
|
+
const absolutePath = path.resolve(baseDir, inputPath);
|
|
509
|
+
const pathProbe = probeRequestedPath(absolutePath);
|
|
510
|
+
return {
|
|
511
|
+
requestIndex: index + 1,
|
|
512
|
+
inputPath,
|
|
513
|
+
absolutePath,
|
|
514
|
+
canonicalPath: canonicalizeTokenPath(absolutePath, baseDir),
|
|
515
|
+
pathProbe,
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
const readableEntries = requestedEntries.filter((entry) => entry.pathProbe.isFile);
|
|
519
|
+
const measuredResults = countFilesMetrics(readableEntries.map((entry) => entry.absolutePath), encodingName);
|
|
520
|
+
const measuredByRequestIndex = new Map<number, CountFileMetricsResult>();
|
|
521
|
+
readableEntries.forEach((entry, index) => {
|
|
522
|
+
measuredByRequestIndex.set(entry.requestIndex, measuredResults[index]!);
|
|
523
|
+
});
|
|
524
|
+
const files: TokenToolFileEntry[] = requestedEntries.map((entry) => {
|
|
525
|
+
const measured = measuredByRequestIndex.get(entry.requestIndex);
|
|
526
|
+
const fileExtension = measured?.fileExtension ?? path.extname(entry.absolutePath).toLowerCase();
|
|
527
|
+
const languageName = measured?.languageName ?? inferLanguageName(entry.absolutePath);
|
|
528
|
+
if (!entry.pathProbe.isFile) {
|
|
529
|
+
return {
|
|
530
|
+
request_index: entry.requestIndex,
|
|
531
|
+
input_path: entry.inputPath,
|
|
532
|
+
canonical_path: entry.canonicalPath,
|
|
533
|
+
absolute_path: entry.absolutePath.split(path.sep).join("/"),
|
|
534
|
+
file_name: path.basename(entry.canonicalPath),
|
|
535
|
+
file_extension: fileExtension,
|
|
536
|
+
language_name: languageName,
|
|
537
|
+
status: "skipped",
|
|
538
|
+
exists: entry.pathProbe.exists,
|
|
539
|
+
is_file: false,
|
|
540
|
+
start_line_number: 0,
|
|
541
|
+
end_line_number: 0,
|
|
542
|
+
line_count: 0,
|
|
543
|
+
token_count: 0,
|
|
544
|
+
character_count: 0,
|
|
545
|
+
byte_count: 0,
|
|
546
|
+
token_share: 0,
|
|
547
|
+
character_share: 0,
|
|
548
|
+
byte_share: 0,
|
|
549
|
+
error_message: entry.pathProbe.reason ?? "not a file",
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const status: TokenFileStatus = measured?.error ? "error" : "counted";
|
|
553
|
+
return {
|
|
554
|
+
request_index: entry.requestIndex,
|
|
555
|
+
input_path: entry.inputPath,
|
|
556
|
+
canonical_path: entry.canonicalPath,
|
|
557
|
+
absolute_path: entry.absolutePath.split(path.sep).join("/"),
|
|
558
|
+
file_name: path.basename(entry.canonicalPath),
|
|
559
|
+
file_extension: fileExtension,
|
|
560
|
+
language_name: languageName,
|
|
561
|
+
status,
|
|
562
|
+
exists: true,
|
|
563
|
+
is_file: true,
|
|
564
|
+
start_line_number: measured?.startLineNumber ?? 0,
|
|
565
|
+
end_line_number: measured?.endLineNumber ?? 0,
|
|
566
|
+
line_count: measured?.lines ?? 0,
|
|
567
|
+
primary_heading_text: measured?.primaryHeadingText,
|
|
568
|
+
doxygen_file_fields: measured?.doxygenFileFields,
|
|
569
|
+
token_count: measured?.tokens ?? 0,
|
|
570
|
+
character_count: measured?.chars ?? 0,
|
|
571
|
+
byte_count: measured?.bytes ?? 0,
|
|
572
|
+
token_share: 0,
|
|
573
|
+
character_share: 0,
|
|
574
|
+
byte_share: 0,
|
|
575
|
+
error_message: measured?.error,
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
const totalTokenCount = files
|
|
579
|
+
.filter((entry) => entry.status === "counted")
|
|
580
|
+
.reduce((sum, entry) => sum + entry.token_count, 0);
|
|
581
|
+
const totalCharacterCount = files
|
|
582
|
+
.filter((entry) => entry.status === "counted")
|
|
583
|
+
.reduce((sum, entry) => sum + entry.character_count, 0);
|
|
584
|
+
const totalByteCount = files
|
|
585
|
+
.filter((entry) => entry.status === "counted")
|
|
586
|
+
.reduce((sum, entry) => sum + entry.byte_count, 0);
|
|
587
|
+
const totalLineCount = files
|
|
588
|
+
.filter((entry) => entry.status === "counted")
|
|
589
|
+
.reduce((sum, entry) => sum + entry.line_count, 0);
|
|
590
|
+
const filesWithShares = files.map((entry) => ({
|
|
591
|
+
...entry,
|
|
592
|
+
token_share: roundRatio(entry.token_count, totalTokenCount),
|
|
593
|
+
character_share: roundRatio(entry.character_count, totalCharacterCount),
|
|
594
|
+
byte_share: roundRatio(entry.byte_count, totalByteCount),
|
|
595
|
+
}));
|
|
596
|
+
const countedFileCount = filesWithShares.filter((entry) => entry.status === "counted").length;
|
|
597
|
+
const errorFileCount = filesWithShares.filter((entry) => entry.status === "error").length;
|
|
598
|
+
const skippedFileCount = filesWithShares.filter((entry) => entry.status === "skipped").length;
|
|
599
|
+
const countedPathsByTokenCountDesc = orderPathsByMetric(filesWithShares, (entry) => entry.token_count, "desc");
|
|
600
|
+
const countedPathsByTokenCountAsc = orderPathsByMetric(filesWithShares, (entry) => entry.token_count, "asc");
|
|
601
|
+
const countedPathsByLineCountDesc = orderPathsByMetric(filesWithShares, (entry) => entry.line_count, "desc");
|
|
602
|
+
const dominantTokenFileEntry = filesWithShares
|
|
603
|
+
.filter((entry) => entry.status === "counted")
|
|
604
|
+
.sort((left, right) => {
|
|
605
|
+
if (left.token_count === right.token_count) {
|
|
606
|
+
return left.canonical_path.localeCompare(right.canonical_path);
|
|
607
|
+
}
|
|
608
|
+
return right.token_count - left.token_count;
|
|
609
|
+
})[0];
|
|
610
|
+
const skippedInputs = filesWithShares
|
|
611
|
+
.filter((entry) => entry.status === "skipped" && entry.error_message)
|
|
612
|
+
.map((entry) => ({
|
|
613
|
+
input_path: entry.input_path,
|
|
614
|
+
canonical_path: entry.canonical_path,
|
|
615
|
+
reason: entry.error_message!,
|
|
616
|
+
}));
|
|
617
|
+
const errorInputs = filesWithShares
|
|
618
|
+
.filter((entry) => entry.status === "error" && entry.error_message)
|
|
619
|
+
.map((entry) => ({
|
|
620
|
+
input_path: entry.input_path,
|
|
621
|
+
canonical_path: entry.canonical_path,
|
|
622
|
+
reason: entry.error_message!,
|
|
623
|
+
}));
|
|
624
|
+
const derivedRecommendations: TokenToolRecommendation[] = countedPathsByTokenCountDesc.length > 0
|
|
625
|
+
? [
|
|
626
|
+
{
|
|
627
|
+
kind: "prioritize_high_token_paths",
|
|
628
|
+
basis_metric_name: "token_count",
|
|
629
|
+
ordered_paths: countedPathsByTokenCountDesc,
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
kind: "defer_low_token_paths",
|
|
633
|
+
basis_metric_name: "token_count",
|
|
634
|
+
ordered_paths: countedPathsByTokenCountAsc,
|
|
635
|
+
},
|
|
636
|
+
]
|
|
637
|
+
: [];
|
|
638
|
+
const actionableNextSteps: TokenToolNextStepHint[] = countedPathsByTokenCountDesc.length > 0
|
|
639
|
+
? [
|
|
640
|
+
{
|
|
641
|
+
kind: "read_top_token_paths_first",
|
|
642
|
+
ordered_paths: countedPathsByTokenCountDesc.slice(0, 3),
|
|
643
|
+
goal: "minimize context-truncation risk during initial review",
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
kind: "reserve_low_token_paths_for_follow_up",
|
|
647
|
+
ordered_paths: countedPathsByTokenCountAsc.slice(0, 3),
|
|
648
|
+
goal: "defer lower-cost files until high-cost files have been reviewed",
|
|
649
|
+
},
|
|
650
|
+
]
|
|
651
|
+
: [];
|
|
652
|
+
return {
|
|
653
|
+
request: {
|
|
654
|
+
tool_name: options.toolName,
|
|
655
|
+
scope: options.scope,
|
|
656
|
+
encoding_name: encodingName,
|
|
657
|
+
base_dir_path: baseDir.split(path.sep).join("/"),
|
|
658
|
+
requested_file_count: options.requestedPaths.length,
|
|
659
|
+
requested_input_paths: options.requestedPaths,
|
|
660
|
+
requested_canonical_paths: requestedEntries.map((entry) => entry.canonicalPath),
|
|
661
|
+
docs_dir_path: options.docsDir,
|
|
662
|
+
canonical_doc_names: options.canonicalDocNames,
|
|
663
|
+
},
|
|
664
|
+
summary: {
|
|
665
|
+
processable_file_count: countedFileCount + errorFileCount,
|
|
666
|
+
counted_file_count: countedFileCount,
|
|
667
|
+
error_file_count: errorFileCount,
|
|
668
|
+
skipped_file_count: skippedFileCount,
|
|
669
|
+
total_token_count: totalTokenCount,
|
|
670
|
+
total_character_count: totalCharacterCount,
|
|
671
|
+
total_byte_count: totalByteCount,
|
|
672
|
+
total_line_count: totalLineCount,
|
|
673
|
+
average_token_count_per_counted_file: countedFileCount === 0 ? 0 : Number((totalTokenCount / countedFileCount).toFixed(6)),
|
|
674
|
+
average_character_count_per_counted_file: countedFileCount === 0 ? 0 : Number((totalCharacterCount / countedFileCount).toFixed(6)),
|
|
675
|
+
average_byte_count_per_counted_file: countedFileCount === 0 ? 0 : Number((totalByteCount / countedFileCount).toFixed(6)),
|
|
676
|
+
average_line_count_per_counted_file: countedFileCount === 0 ? 0 : Number((totalLineCount / countedFileCount).toFixed(6)),
|
|
677
|
+
},
|
|
678
|
+
files: filesWithShares,
|
|
679
|
+
guidance: {
|
|
680
|
+
source_observations: {
|
|
681
|
+
counted_paths_by_token_count_desc: countedPathsByTokenCountDesc,
|
|
682
|
+
counted_paths_by_line_count_desc: countedPathsByLineCountDesc,
|
|
683
|
+
skipped_inputs: skippedInputs,
|
|
684
|
+
error_inputs: errorInputs,
|
|
685
|
+
dominant_token_file: dominantTokenFileEntry
|
|
686
|
+
? {
|
|
687
|
+
canonical_path: dominantTokenFileEntry.canonical_path,
|
|
688
|
+
token_count: dominantTokenFileEntry.token_count,
|
|
689
|
+
token_share: dominantTokenFileEntry.token_share,
|
|
690
|
+
}
|
|
691
|
+
: undefined,
|
|
692
|
+
},
|
|
693
|
+
derived_recommendations: derivedRecommendations,
|
|
694
|
+
actionable_next_steps: actionableNextSteps,
|
|
695
|
+
},
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* @brief Formats per-file token metrics as a human-readable summary block.
|
|
701
|
+
* @details Aggregates totals, emits one status line per file, and appends a summary footer containing file, token, and character counts. Runtime is O(n). No external state is mutated.
|
|
702
|
+
* @param[in] results {CountFileMetricsResult[]} Per-file metric records.
|
|
703
|
+
* @return {string} Multiline summary suitable for CLI or editor output.
|
|
704
|
+
*/
|
|
705
|
+
export function formatPackSummary(results: CountFileMetricsResult[]): string {
|
|
706
|
+
const lines: string[] = [];
|
|
707
|
+
let totalTokens = 0;
|
|
708
|
+
let totalChars = 0;
|
|
709
|
+
const totalFiles = results.length;
|
|
710
|
+
|
|
711
|
+
for (const result of results) {
|
|
712
|
+
const fileName = path.basename(result.file);
|
|
713
|
+
totalTokens += result.tokens;
|
|
714
|
+
totalChars += result.chars;
|
|
715
|
+
if (result.error) {
|
|
716
|
+
lines.push(` ❌ ${fileName}: ERROR - ${result.error}`);
|
|
717
|
+
} else {
|
|
718
|
+
lines.push(` 📄 ${fileName}: ${result.tokens.toLocaleString()} tokens, ${result.chars.toLocaleString()} chars`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
lines.push("");
|
|
723
|
+
lines.push("📊 Pack Summary:");
|
|
724
|
+
lines.push("────────────────");
|
|
725
|
+
lines.push(` Total Files: ${totalFiles} files`);
|
|
726
|
+
lines.push(` Total Tokens: ${totalTokens.toLocaleString()} tokens`);
|
|
727
|
+
lines.push(` Total Chars: ${totalChars.toLocaleString()} chars`);
|
|
728
|
+
return lines.join("\n");
|
|
729
|
+
}
|