pi-usereq 0.4.0 → 0.6.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.
Files changed (65) hide show
  1. package/.gitignore +3 -0
  2. package/CHANGELOG.md +41 -0
  3. package/README.md +1 -1
  4. package/package.json +1 -1
  5. package/{req → pi-usereq}/docs/REFERENCES.md +499 -491
  6. package/{req → pi-usereq}/docs/REQUIREMENTS.md +84 -62
  7. package/{req → pi-usereq}/docs/WORKFLOW.md +87 -102
  8. package/src/core/agent-tool-json.ts +42 -193
  9. package/src/core/compress-payload.ts +7 -15
  10. package/src/core/config.ts +39 -12
  11. package/src/core/extension-status.ts +94 -58
  12. package/src/core/find-payload.ts +21 -44
  13. package/src/core/pi-notify.ts +198 -11
  14. package/src/core/reference-payload.ts +6 -14
  15. package/src/core/runtime-project-paths.ts +3 -32
  16. package/src/core/settings-menu.ts +9 -4
  17. package/src/core/static-check.ts +35 -171
  18. package/src/core/token-counter.ts +3 -121
  19. package/src/index.ts +339 -180
  20. package/tests/attended-results-scenarios.ts +3 -13
  21. package/tests/cli-command-option-parity.test.ts +21 -12
  22. package/tests/debug-extension-harness.test.ts +20 -22
  23. package/tests/extension-registration.test.ts +394 -157
  24. package/tests/oracle-project.test.ts +8 -3
  25. package/tests/oracle-standalone.test.ts +7 -13
  26. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +0 -5
  27. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +0 -5
  28. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +0 -5
  29. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +0 -5
  30. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +0 -5
  31. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +0 -5
  32. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +0 -5
  33. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +0 -5
  34. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +0 -5
  35. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +0 -5
  36. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +0 -5
  37. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +0 -5
  38. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +0 -5
  39. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +0 -5
  40. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +0 -5
  41. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +0 -5
  42. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +0 -5
  43. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +0 -5
  44. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +0 -5
  45. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +0 -5
  46. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +0 -5
  47. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +0 -5
  48. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +0 -5
  49. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +0 -5
  50. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +0 -5
  51. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +0 -5
  52. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +0 -5
  53. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +0 -5
  54. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +0 -5
  55. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +0 -5
  56. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +0 -5
  57. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +0 -5
  58. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +0 -5
  59. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +0 -5
  60. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +0 -5
  61. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +0 -5
  62. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +0 -5
  63. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +0 -5
  64. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +0 -5
  65. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +0 -5
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file
3
3
  * @brief Defines static-check language mappings and checker dispatch implementations.
4
- * @details Parses static-check configuration syntax, resolves file targets, and runs built-in or command-based analyzers such as Pylance and Ruff. Runtime is linear in file count plus external tool cost. Side effects include filesystem reads, PATH probing, process spawning, and console output.
4
+ * @details Parses Command-only user static-check specifications, preserves debug `Dummy` config handling, resolves file targets, and runs modular dummy or command-based analyzers. Runtime is linear in file count plus external tool cost. Side effects include filesystem reads, PATH probing, process spawning, and console output.
5
5
  */
6
6
 
7
7
  import fs from "node:fs";
@@ -73,10 +73,16 @@ export const STATIC_CHECK_EXT_TO_LANG: Record<string, string> = {
73
73
  };
74
74
 
75
75
  /**
76
- * @brief Lists the built-in static-check module identifiers.
77
- * @details The tuple constrains configuration parsing and menu rendering to supported checker implementations. Access complexity is O(1).
76
+ * @brief Lists the user-configurable static-check module identifiers.
77
+ * @details The tuple constrains guided configuration and `--enable-static-check` parsing to the supported user-facing checker implementations. Access complexity is O(1).
78
78
  */
79
- export const STATIC_CHECK_MODULES = ["Dummy", "Pylance", "Ruff", "Command"] as const;
79
+ export const STATIC_CHECK_MODULES = ["Command"] as const;
80
+
81
+ /**
82
+ * @brief Lists the persisted or debug-capable static-check module identifiers.
83
+ * @details The tuple augments user-configurable modules with debug-only `Dummy` support used by existing config payloads and the standalone test driver. Access complexity is O(1).
84
+ */
85
+ const STATIC_CHECK_PERSISTED_MODULES = ["Dummy", "Command"] as const;
80
86
 
81
87
  /**
82
88
  * @brief Describes supported extensions for one canonical static-check language.
@@ -88,13 +94,10 @@ export interface StaticCheckLanguageSupport {
88
94
  }
89
95
 
90
96
  /**
91
- * @brief Maps case-insensitive module names to canonical module identifiers.
92
- * @details Enables permissive user input while keeping downstream dispatch logic deterministic. Lookup complexity is O(1).
97
+ * @brief Maps case-insensitive user module names to canonical static-check module identifiers.
98
+ * @details Enables permissive Command-only user input while keeping downstream dispatch logic deterministic. Lookup complexity is O(1).
93
99
  */
94
100
  const CANONICAL_MODULES: Record<string, (typeof STATIC_CHECK_MODULES)[number]> = {
95
- dummy: "Dummy",
96
- pylance: "Pylance",
97
- ruff: "Ruff",
98
101
  command: "Command",
99
102
  };
100
103
 
@@ -126,14 +129,23 @@ export function getSupportedStaticCheckLanguageSupport(): StaticCheckLanguageSup
126
129
  }
127
130
 
128
131
  /**
129
- * @brief Formats the supported module list for diagnostics.
130
- * @details Joins `STATIC_CHECK_MODULES` with commas for direct insertion into error strings. Time complexity is O(n). No side effects occur.
131
- * @return {string} Comma-delimited module names.
132
+ * @brief Formats the user-configurable module list for diagnostics.
133
+ * @details Joins `STATIC_CHECK_MODULES` with commas for direct insertion into user-facing error strings. Time complexity is O(n). No side effects occur.
134
+ * @return {string} Comma-delimited user-configurable module names.
132
135
  */
133
136
  function formatStaticCheckModules(): string {
134
137
  return STATIC_CHECK_MODULES.join(", ");
135
138
  }
136
139
 
140
+ /**
141
+ * @brief Formats the persisted or debug-capable module list for dispatch diagnostics.
142
+ * @details Joins `STATIC_CHECK_PERSISTED_MODULES` with commas for error strings emitted while executing existing config entries or debug-driver requests. Time complexity is O(n). No side effects occur.
143
+ * @return {string} Comma-delimited persisted module names.
144
+ */
145
+ function formatDispatchStaticCheckModules(): string {
146
+ return STATIC_CHECK_PERSISTED_MODULES.join(", ");
147
+ }
148
+
137
149
  /**
138
150
  * @brief Splits a comma-delimited static-check specification while honoring quotes.
139
151
  * @details Performs a single pass over the right-hand side of `LANG=...`, preserving commas inside quoted segments. Runtime is O(n). No side effects occur.
@@ -165,11 +177,11 @@ function splitCsvLikeTokens(specRhs: string): string[] {
165
177
  }
166
178
 
167
179
  /**
168
- * @brief Parses one `LANG=MODULE[,CMD[,PARAM...]]` static-check specification.
169
- * @details Validates the language alias, canonicalizes the module name, enforces module-specific argument requirements, and returns a config entry ready for persistence. Runtime is O(n) in specification length. No external state is mutated.
180
+ * @brief Parses one `LANG=Command,CMD[,PARAM...]` static-check specification.
181
+ * @details Validates the language alias, canonicalizes the Command module name, enforces the required executable argument, and returns a config entry ready for persistence. Runtime is O(n) in specification length. No external state is mutated.
170
182
  * @param[in] spec {string} Raw static-check specification string.
171
183
  * @return {[string, StaticCheckEntry]} Tuple of canonical language name and normalized checker configuration.
172
- * @throws {ReqError} Throws for missing separators, unknown languages, unknown modules, or missing required command arguments.
184
+ * @throws {ReqError} Throws for missing separators, unknown languages, non-Command modules, or missing required command arguments.
173
185
  */
174
186
  export function parseEnableStaticCheck(spec: string): [string, StaticCheckEntry] {
175
187
  if (!spec.includes("=")) {
@@ -290,8 +302,8 @@ function resolveFiles(inputs: string[]): string[] {
290
302
  }
291
303
 
292
304
  /**
293
- * @brief Provides the base implementation for file-oriented static checks.
294
- * @details Resolves input files once, emits standardized headers, and defines overridable `checkFile` and `emitLine` hooks used by concrete analyzers. Runtime is O(f) plus subclass checker cost. Side effects include console output.
305
+ * @brief Provides the shared and debug-capable base implementation for file-oriented static checks.
306
+ * @details Resolves input files once, emits standardized headers, implements the debug `Dummy` checker behavior, and defines overridable `checkFile` plus `emitLine` hooks used by concrete analyzers. Runtime is O(f) plus subclass checker cost. Side effects include console output.
295
307
  */
296
308
  export class StaticCheckBase {
297
309
  static LABEL = "Dummy";
@@ -370,146 +382,7 @@ export class StaticCheckBase {
370
382
  }
371
383
 
372
384
  /**
373
- * @brief Resolves the preferred Python executable for Python-based checkers.
374
- * @details Checks the project virtual environment first, then `PI_USEREQ_PYTHON`, then `python3`, then `python`, and finally falls back to the literal `python3` string. Runtime is O(c) in candidate count. Side effects are filesystem reads and PATH probing.
375
- * @param[in] projectBase {string | undefined} Optional project root used to probe `.venv/bin/python`.
376
- * @return {string} Executable path or command name.
377
- */
378
- function detectPythonExecutable(projectBase?: string): string {
379
- const candidates = [
380
- projectBase ? path.join(projectBase, ".venv", "bin", "python") : undefined,
381
- process.env.PI_USEREQ_PYTHON,
382
- "python3",
383
- "python",
384
- ].filter((value): value is string => !!value);
385
-
386
- for (const candidate of candidates) {
387
- if (candidate.includes(path.sep)) {
388
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
389
- return candidate;
390
- }
391
- continue;
392
- }
393
- if (findExecutable(candidate)) {
394
- return candidate;
395
- }
396
- }
397
- return "python3";
398
- }
399
-
400
- /**
401
- * @brief Runs Pyright/Pylance checks through the selected Python interpreter.
402
- * @details Invokes `python -m pyright` for each resolved file and emits standardized OK/FAIL records. Runtime is dominated by external checker execution. Side effects include process spawning and console output.
403
- */
404
- export class StaticCheckPylance extends StaticCheckBase {
405
- static override LABEL = "Pylance";
406
- private projectBase?: string;
407
-
408
- /**
409
- * @brief Initializes a Pylance checker instance.
410
- * @details Delegates file resolution to the base class, stores the optional project base, and overrides the human-readable label. Runtime is O(f) in resolved input count. Mutates instance fields only.
411
- * @param[in] inputs {string[]} Raw file inputs.
412
- * @param[in] extraArgs {string[] | undefined} Extra Pyright arguments.
413
- * @param[in] failOnly {boolean} When `true`, suppress successful-file output.
414
- * @param[in] projectBase {string | undefined} Optional project root used for interpreter resolution and process cwd.
415
- */
416
- constructor(inputs: string[], extraArgs?: string[], failOnly = false, projectBase?: string) {
417
- super(inputs, extraArgs, failOnly);
418
- this.projectBase = projectBase;
419
- this.label = "Pylance";
420
- }
421
-
422
- /**
423
- * @brief Runs Pyright for one file.
424
- * @details Resolves the Python interpreter, executes `python -m pyright`, and emits standardized OK/FAIL output with captured evidence on failure. Runtime is dominated by external tool execution. Side effects include process spawning and console output.
425
- * @param[in] filePath {string} File to analyze.
426
- * @return {number} `0` on success; `1` on execution or analysis failure.
427
- */
428
- protected override checkFile(filePath: string): number {
429
- const pythonExec = detectPythonExecutable(this.projectBase);
430
- const command = [pythonExec, "-m", "pyright", "--pythonpath", pythonExec, filePath, ...this.extraArgs];
431
- const result = spawnSync(command[0]!, command.slice(1), {
432
- cwd: this.projectBase,
433
- encoding: "utf8",
434
- });
435
- if (result.error) {
436
- this.emitLine(this.headerLine(filePath));
437
- this.emitLine("Result: FAIL");
438
- this.emitLine("Evidence:");
439
- this.emitLine(" pyright module not available via sys.executable");
440
- return 1;
441
- }
442
- if (result.status === 0) {
443
- if (!this.failOnly) {
444
- this.emitLine(this.headerLine(filePath));
445
- this.emitLine("Result: OK");
446
- }
447
- return 0;
448
- }
449
- this.emitLine(this.headerLine(filePath));
450
- this.emitLine("Result: FAIL");
451
- this.emitLine("Evidence:");
452
- this.emitLine(`${result.stdout ?? ""}${result.stderr ?? ""}`.trimEnd());
453
- return 1;
454
- }
455
- }
456
-
457
- /**
458
- * @brief Runs Ruff checks through the selected Python interpreter.
459
- * @details Invokes `python -m ruff check` for each resolved file and emits standardized OK/FAIL records. Runtime is dominated by external checker execution. Side effects include process spawning and console output.
460
- */
461
- export class StaticCheckRuff extends StaticCheckBase {
462
- static override LABEL = "Ruff";
463
- private projectBase?: string;
464
-
465
- /**
466
- * @brief Initializes a Ruff checker instance.
467
- * @details Delegates file resolution to the base class, stores the optional project base, and overrides the human-readable label. Runtime is O(f) in resolved input count. Mutates instance fields only.
468
- * @param[in] inputs {string[]} Raw file inputs.
469
- * @param[in] extraArgs {string[] | undefined} Extra Ruff arguments.
470
- * @param[in] failOnly {boolean} When `true`, suppress successful-file output.
471
- * @param[in] projectBase {string | undefined} Optional project root used for interpreter resolution and process cwd.
472
- */
473
- constructor(inputs: string[], extraArgs?: string[], failOnly = false, projectBase?: string) {
474
- super(inputs, extraArgs, failOnly);
475
- this.label = "Ruff";
476
- this.projectBase = projectBase;
477
- }
478
-
479
- /**
480
- * @brief Runs Ruff for one file.
481
- * @details Resolves the Python interpreter, executes `python -m ruff check`, and emits standardized OK/FAIL output with captured evidence on failure. Runtime is dominated by external tool execution. Side effects include process spawning and console output.
482
- * @param[in] filePath {string} File to analyze.
483
- * @return {number} `0` on success; `1` on execution or analysis failure.
484
- */
485
- protected override checkFile(filePath: string): number {
486
- const pythonExec = detectPythonExecutable(this.projectBase);
487
- const command = [pythonExec, "-m", "ruff", "check", filePath, ...this.extraArgs];
488
- const result = spawnSync(command[0]!, command.slice(1), { encoding: "utf8", cwd: this.projectBase });
489
- if (result.error) {
490
- this.emitLine(this.headerLine(filePath));
491
- this.emitLine("Result: FAIL");
492
- this.emitLine("Evidence:");
493
- this.emitLine(" ruff module not available via sys.executable");
494
- return 1;
495
- }
496
- if (result.status === 0) {
497
- if (!this.failOnly) {
498
- this.emitLine(this.headerLine(filePath));
499
- this.emitLine("Result: OK");
500
- }
501
- return 0;
502
- }
503
- this.emitLine(this.headerLine(filePath));
504
- this.emitLine("Result: FAIL");
505
- this.emitLine("Evidence:");
506
- this.emitLine(`${result.stdout ?? ""}${result.stderr ?? ""}`.trimEnd());
507
- return 1;
508
- }
509
- }
510
-
511
- /**
512
- * @brief Runs an arbitrary external command as a static checker.
385
+ * @brief Runs the user-facing external-command static checker.
513
386
  * @details Validates command availability on PATH during construction, then invokes the command with configured extra arguments plus one target file at a time. Runtime is dominated by external command execution. Side effects include PATH probing, process spawning, and console output.
514
387
  */
515
388
  export class StaticCheckCommand extends StaticCheckBase {
@@ -602,7 +475,7 @@ function findExecutable(cmd: string): string | undefined {
602
475
 
603
476
  /**
604
477
  * @brief Dispatches one configured static checker for a single file.
605
- * @details Selects the checker implementation by module name, normalizes parameter arrays, and runs exactly one checker instance against the target file. Runtime is dominated by the selected checker. Side effects include console output and possible process spawning.
478
+ * @details Selects the debug `Dummy` or user-facing `Command` implementation by module name, normalizes parameter arrays, and runs exactly one checker instance against the target file. Runtime is dominated by the selected checker. Side effects include console output and possible process spawning.
606
479
  * @param[in] filePath {string} Absolute or relative file path to check.
607
480
  * @param[in] langConfig {StaticCheckEntry} Normalized static-check configuration entry.
608
481
  * @param[in] options {{ failOnly?: boolean; projectBase?: string }} Optional execution controls.
@@ -618,14 +491,9 @@ export function dispatchStaticCheckForFile(
618
491
  const params = Array.isArray(langConfig.params) ? langConfig.params.map(String) : [];
619
492
  const cmd = typeof langConfig.cmd === "string" ? langConfig.cmd : undefined;
620
493
  const failOnly = options.failOnly ?? false;
621
- const projectBase = options.projectBase;
622
494
  switch (moduleName.toLowerCase()) {
623
495
  case "dummy":
624
496
  return new StaticCheckBase([filePath], params, failOnly).run();
625
- case "pylance":
626
- return new StaticCheckPylance([filePath], params, failOnly, projectBase).run();
627
- case "ruff":
628
- return new StaticCheckRuff([filePath], params, failOnly, projectBase).run();
629
497
  case "command":
630
498
  if (!cmd) {
631
499
  throw new ReqError(`Error: Command module requires 'cmd' in static-check config for '${filePath}'.`, 1);
@@ -633,7 +501,7 @@ export function dispatchStaticCheckForFile(
633
501
  return new StaticCheckCommand(cmd, [filePath], params, failOnly).run();
634
502
  default:
635
503
  throw new ReqError(
636
- `Error: unknown static-check module '${moduleName}'. Valid modules: ${formatStaticCheckModules()}`,
504
+ `Error: unknown static-check module '${moduleName}'. Valid modules: ${formatDispatchStaticCheckModules()}`,
637
505
  1,
638
506
  );
639
507
  }
@@ -641,23 +509,19 @@ export function dispatchStaticCheckForFile(
641
509
 
642
510
  /**
643
511
  * @brief Runs the standalone static-check test driver.
644
- * @details Dispatches subcommands to the built-in checker implementations without consulting project configuration. Runtime is O(n) in argument count plus checker cost. Side effects include console output and external process spawning.
512
+ * @details Dispatches debug `dummy` or user-facing `command` subcommands without consulting project configuration. Runtime is O(n) in argument count plus checker cost. Side effects include console output and external process spawning.
645
513
  * @param[in] argv {string[]} Raw static-check subcommand arguments.
646
514
  * @return {number} Checker exit status where `0` means success.
647
515
  * @throws {ReqError} Throws when no subcommand is provided, the subcommand is unknown, or required arguments are missing.
648
516
  */
649
517
  export function runStaticCheck(argv: string[]): number {
650
518
  if (argv.length === 0) {
651
- throw new ReqError("Error: --test-static-check requires a subcommand: dummy, pylance, ruff, command.", 1);
519
+ throw new ReqError("Error: --test-static-check requires a subcommand: dummy, command.", 1);
652
520
  }
653
521
  const [subcommand, ...rest] = argv;
654
522
  switch (subcommand) {
655
523
  case "dummy":
656
524
  return new StaticCheckBase(rest).run();
657
- case "pylance":
658
- return new StaticCheckPylance(rest).run();
659
- case "ruff":
660
- return new StaticCheckRuff(rest, undefined, false, process.cwd()).run();
661
525
  case "command": {
662
526
  if (rest.length === 0) {
663
527
  throw new ReqError("Error: --test-static-check command requires a <cmd> argument.", 1);
@@ -667,7 +531,7 @@ export function runStaticCheck(argv: string[]): number {
667
531
  }
668
532
  default:
669
533
  throw new ReqError(
670
- `Error: unknown --test-static-check subcommand '${subcommand}'. Valid subcommands: dummy, pylance, ruff, command.`,
534
+ `Error: unknown --test-static-check subcommand '${subcommand}'. Valid subcommands: dummy, command.`,
671
535
  1,
672
536
  );
673
537
  }
@@ -175,13 +175,11 @@ export interface TokenToolGuidanceSection {
175
175
 
176
176
  /**
177
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.
178
+ * @details Exposes only aggregate numeric totals plus per-file metrics, omitting request echoes and derived guidance that can be inferred from tool registration or recomputed by the caller. The interface is compile-time only and introduces no runtime cost.
179
179
  */
180
180
  export interface TokenToolPayload {
181
- request: TokenToolRequestSection;
182
181
  summary: TokenToolSummarySection;
183
182
  files: TokenToolFileEntry[];
184
- guidance: TokenToolGuidanceSection;
185
183
  }
186
184
 
187
185
  /**
@@ -359,41 +357,6 @@ function roundRatio(numerator: number, denominator: number): number {
359
357
  return Number((numerator / denominator).toFixed(6));
360
358
  }
361
359
 
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
360
  /**
398
361
  * @brief Probes one requested path before token counting.
399
362
  * @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.
@@ -496,9 +459,9 @@ export function countFilesMetrics(filePaths: string[], encodingName = TOKEN_COUN
496
459
 
497
460
  /**
498
461
  * @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.
462
+ * @details Validates requested paths against the filesystem, counts token metrics for processable files, preserves caller order in the file table, and emits direct-access file facts such as sizes, headings, and optional Doxygen file fields while omitting request echoes and derived guidance. Runtime is O(F + S). Side effects are limited to filesystem reads.
500
463
  * @param[in] options {BuildTokenToolPayloadOptions} Payload-construction options.
501
- * @return {TokenToolPayload} Structured token payload ordered as request, summary, files, guidance.
464
+ * @return {TokenToolPayload} Structured token payload ordered as summary then files.
502
465
  * @satisfies REQ-010, REQ-017, REQ-069, REQ-070, REQ-071, REQ-073, REQ-074, REQ-075
503
466
  */
504
467
  export function buildTokenToolPayload(options: BuildTokenToolPayloadOptions): TokenToolPayload {
@@ -596,71 +559,7 @@ export function buildTokenToolPayload(options: BuildTokenToolPayloadOptions): To
596
559
  const countedFileCount = filesWithShares.filter((entry) => entry.status === "counted").length;
597
560
  const errorFileCount = filesWithShares.filter((entry) => entry.status === "error").length;
598
561
  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
562
  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
563
  summary: {
665
564
  processable_file_count: countedFileCount + errorFileCount,
666
565
  counted_file_count: countedFileCount,
@@ -676,23 +575,6 @@ export function buildTokenToolPayload(options: BuildTokenToolPayloadOptions): To
676
575
  average_line_count_per_counted_file: countedFileCount === 0 ? 0 : Number((totalLineCount / countedFileCount).toFixed(6)),
677
576
  },
678
577
  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
578
  };
697
579
  }
698
580