pi-usereq 0.4.0 → 0.5.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 (60) hide show
  1. package/.gitignore +3 -0
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +1 -1
  4. package/package.json +1 -1
  5. package/{req → pi-usereq}/docs/REFERENCES.md +196 -159
  6. package/{req → pi-usereq}/docs/REQUIREMENTS.md +65 -44
  7. package/{req → pi-usereq}/docs/WORKFLOW.md +66 -73
  8. package/src/core/config.ts +39 -12
  9. package/src/core/extension-status.ts +94 -58
  10. package/src/core/pi-notify.ts +198 -11
  11. package/src/core/runtime-project-paths.ts +3 -32
  12. package/src/core/settings-menu.ts +9 -4
  13. package/src/core/static-check.ts +35 -171
  14. package/src/index.ts +281 -86
  15. package/tests/attended-results-scenarios.ts +3 -13
  16. package/tests/cli-command-option-parity.test.ts +21 -12
  17. package/tests/debug-extension-harness.test.ts +11 -7
  18. package/tests/extension-registration.test.ts +371 -71
  19. package/tests/oracle-project.test.ts +8 -3
  20. package/tests/oracle-standalone.test.ts +7 -13
  21. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +0 -5
  22. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +0 -5
  23. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +0 -5
  24. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +0 -5
  25. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +0 -5
  26. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +0 -5
  27. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +0 -5
  28. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +0 -5
  29. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +0 -5
  30. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +0 -5
  31. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +0 -5
  32. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +0 -5
  33. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +0 -5
  34. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +0 -5
  35. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +0 -5
  36. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +0 -5
  37. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +0 -5
  38. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +0 -5
  39. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +0 -5
  40. package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +0 -5
  41. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +0 -5
  42. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +0 -5
  43. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +0 -5
  44. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +0 -5
  45. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +0 -5
  46. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +0 -5
  47. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +0 -5
  48. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +0 -5
  49. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +0 -5
  50. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +0 -5
  51. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +0 -5
  52. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +0 -5
  53. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +0 -5
  54. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +0 -5
  55. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +0 -5
  56. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +0 -5
  57. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +0 -5
  58. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +0 -5
  59. package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +0 -5
  60. 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
  }