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
@@ -357,16 +357,6 @@ function buildStandaloneScenarios(): AttendedScenario[] {
357
357
  path.posix.join("standalone", "test-static-check-dummy", `${fixtureName}.json`),
358
358
  ["--test-static-check", "dummy", relativeFixturePath],
359
359
  ),
360
- createStandaloneScenario(
361
- `standalone/test-static-check-pylance/${fixtureName}`,
362
- path.posix.join("standalone", "test-static-check-pylance", `${fixtureName}.json`),
363
- ["--test-static-check", "pylance", relativeFixturePath],
364
- ),
365
- createStandaloneScenario(
366
- `standalone/test-static-check-ruff/${fixtureName}`,
367
- path.posix.join("standalone", "test-static-check-ruff", `${fixtureName}.json`),
368
- ["--test-static-check", "ruff", relativeFixturePath],
369
- ),
370
360
  createStandaloneScenario(
371
361
  `standalone/test-static-check-command/${fixtureName}`,
372
362
  path.posix.join("standalone", "test-static-check-command", `${fixtureName}.json`),
@@ -470,14 +460,14 @@ function buildProjectScenarios(): AttendedScenario[] {
470
460
  "--provider",
471
461
  "claude:prompts",
472
462
  "--enable-static-check",
473
- "Python=Pylance",
463
+ "Python=Command,git,--version",
474
464
  ],
475
465
  cwd: projectBase,
476
466
  normalize: createProjectNormalizer(projectBase),
477
467
  cleanup: () => removePath(projectBase),
478
468
  postAssert: () => {
479
469
  const payload = JSON.parse(fs.readFileSync(getProjectConfigPath(projectBase), "utf8")) as UseReqConfig;
480
- assert.deepEqual(payload["static-check"].Python, [{ module: "Pylance" }]);
470
+ assert.deepEqual(payload["static-check"].Python, [{ module: "Command", cmd: "git", params: ["--version"] }]);
481
471
  },
482
472
  };
483
473
  },
@@ -488,7 +478,7 @@ function buildProjectScenarios(): AttendedScenario[] {
488
478
  const script = [
489
479
  "from pathlib import Path",
490
480
  "from usereq.cli import save_config",
491
- `save_config(Path(${JSON.stringify(projectBase)}), 'guidelines', 'docs', 'tests', ['src'], static_check_config={'Python': [{'module': 'Pylance'}]})`,
481
+ `save_config(Path(${JSON.stringify(projectBase)}), 'guidelines', 'docs', 'tests', ['src'], static_check_config={'Python': [{'module': 'Command', 'cmd': 'git', 'params': ['--version']} ]})`,
492
482
  ].join("\n");
493
483
  const result = runPythonInline(script, projectBase);
494
484
  return createProjectNormalizer(projectBase)(toArchivedCliResult(result));
@@ -583,6 +583,14 @@ const TARGET_CASES: TargetParityCase[] = [
583
583
  assert.equal(result.status, 1);
584
584
  assert.match(result.stderr, /not an executable program/);
585
585
  assert.ok(!fs.existsSync(getProjectConfigPath(projectBase)));
586
+
587
+ const removedModule = runNodeCli(
588
+ ["--base", projectBase, "--enable-static-check", "Python=Ruff"],
589
+ projectBase,
590
+ );
591
+ assert.equal(removedModule.status, 1);
592
+ assert.match(removedModule.stderr, /unknown module/i);
593
+ assert.ok(!fs.existsSync(getProjectConfigPath(projectBase)));
586
594
  },
587
595
  },
588
596
  {
@@ -594,16 +602,19 @@ const TARGET_CASES: TargetParityCase[] = [
594
602
  "--base",
595
603
  projectBase,
596
604
  "--enable-static-check",
597
- "Python=Pylance",
605
+ "Python=Command,git,--version",
598
606
  "--enable-static-check",
599
607
  "C=Command,git,--version",
600
608
  "--enable-static-check",
601
- "Python=Ruff",
609
+ "Python=Command,git,--help",
602
610
  ],
603
611
  projectBase,
604
612
  );
605
613
  assert.equal(persisted.status, 0, persisted.stderr);
606
- assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [{ module: "Pylance" }, { module: "Ruff" }]);
614
+ assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [
615
+ { module: "Command", cmd: "git", params: ["--version"] },
616
+ { module: "Command", cmd: "git", params: ["--help"] },
617
+ ]);
607
618
  assert.deepEqual(getStaticCheckEntries(projectBase, "C"), [{ module: "Command", cmd: "git", params: ["--version"] }]);
608
619
  },
609
620
  },
@@ -616,9 +627,9 @@ const TARGET_CASES: TargetParityCase[] = [
616
627
  "--base",
617
628
  projectBase,
618
629
  "--enable-static-check",
619
- "Python=Ruff",
630
+ "Python=Command,git,--version",
620
631
  "--enable-static-check",
621
- "Python=Ruff",
632
+ "Python=Command,git,--version",
622
633
  "--enable-static-check",
623
634
  "C=Command,git,--version",
624
635
  "--enable-static-check",
@@ -627,7 +638,7 @@ const TARGET_CASES: TargetParityCase[] = [
627
638
  projectBase,
628
639
  );
629
640
  assert.equal(result.status, 0, result.stderr);
630
- assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [{ module: "Ruff" }]);
641
+ assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [{ module: "Command", cmd: "git", params: ["--version"] }]);
631
642
  assert.deepEqual(getStaticCheckEntries(projectBase, "C"), [
632
643
  { module: "Command", cmd: "git", params: ["--version"] },
633
644
  { module: "Command", cmd: "git", params: ["--help"] },
@@ -641,7 +652,7 @@ const TARGET_CASES: TargetParityCase[] = [
641
652
  const preloaded = {
642
653
  ...config,
643
654
  "static-check": {
644
- Python: [{ module: "Pylance", meta: "legacy-note" } as unknown as Record<string, unknown>],
655
+ Python: [{ module: "Dummy", meta: "debug-note" } as unknown as Record<string, unknown>],
645
656
  JavaScript: [{ module: "Command", cmd: "git", params: ["--version"] }],
646
657
  } as unknown as UseReqConfig["static-check"],
647
658
  } as UseReqConfig;
@@ -651,9 +662,7 @@ const TARGET_CASES: TargetParityCase[] = [
651
662
  "--base",
652
663
  projectBase,
653
664
  "--enable-static-check",
654
- "Python=Pylance",
655
- "--enable-static-check",
656
- "Python=Ruff",
665
+ "Python=Command,git,--version",
657
666
  "--enable-static-check",
658
667
  "JavaScript=Command,git,--help",
659
668
  ],
@@ -661,8 +670,8 @@ const TARGET_CASES: TargetParityCase[] = [
661
670
  );
662
671
  assert.equal(result.status, 0, result.stderr);
663
672
  assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [
664
- { module: "Pylance", meta: "legacy-note" },
665
- { module: "Ruff" },
673
+ { module: "Dummy" },
674
+ { module: "Command", cmd: "git", params: ["--version"] },
666
675
  ]);
667
676
  assert.deepEqual(getStaticCheckEntries(projectBase, "JavaScript"), [
668
677
  { module: "Command", cmd: "git", params: ["--version"] },
@@ -165,9 +165,9 @@ test("inspectExtension surfaces agent-oriented find tool descriptions and schema
165
165
 
166
166
  assert.ok(filesFind, "missing files-find tool");
167
167
  assert.ok(find, "missing find tool");
168
- assert.match(filesFind.description ?? "", /LLM-oriented JSON payload/);
168
+ assert.match(filesFind.description ?? "", /token-optimized JSON payload/);
169
169
  assert.ok(filesFind.promptGuidelines?.some((line) => line.includes("Supported tags [Typescript]:")));
170
- assert.match(String((filesFind.parameters as { description?: string } | undefined)?.description ?? ""), /supported_tags_by_language/);
170
+ assert.match(String((filesFind.parameters as { description?: string } | undefined)?.description ?? ""), /Static supported-tag matrices are documented in tool registration metadata/);
171
171
  assert.match(find.description ?? "", /configured project source directories/);
172
172
  assert.ok(find.promptGuidelines?.some((line) => line.includes("Regex rule:")));
173
173
  assert.match(String((find.parameters as { description?: string } | undefined)?.description ?? ""), /Regex matches construct names only/);
@@ -185,7 +185,7 @@ test("inspectExtension surfaces agent-oriented compression tool descriptions and
185
185
 
186
186
  assert.ok(filesCompress, "missing files-compress tool");
187
187
  assert.ok(compress, "missing compress tool");
188
- assert.match(filesCompress.description ?? "", /request, summary, repository, files, and execution sections/);
188
+ assert.match(filesCompress.description ?? "", /summary, repository, files, and execution sections/);
189
189
  assert.ok(filesCompress.promptGuidelines?.some((line) => line.includes("Line-number behavior:")));
190
190
  assert.match(String((filesCompress.parameters as { description?: string } | undefined)?.description ?? ""), /structured compressed lines/);
191
191
  assert.match(compress.description ?? "", /configured project source directories/);
@@ -212,7 +212,7 @@ test("inspectExtension surfaces structured utility tool descriptions and schema
212
212
  assert.ok(docsCheck, "missing docs-check tool");
213
213
  assert.ok(gitWtCreate, "missing git-wt-create tool");
214
214
 
215
- assert.match(gitPath.description ?? "", /JSON-first payload/);
215
+ assert.match(gitPath.description ?? "", /token-optimized JSON payload/);
216
216
  assert.ok(gitPath.promptGuidelines?.some((line) => line.includes("Output contract:")));
217
217
  assert.match(String((gitPath.parameters as { description?: string } | undefined)?.description ?? ""), /path_value/);
218
218
 
@@ -220,9 +220,9 @@ test("inspectExtension surfaces structured utility tool descriptions and schema
220
220
  assert.ok(filesStaticCheck.promptGuidelines?.some((line) => line.includes("Failure contract:")));
221
221
  assert.match(String((filesStaticCheck.parameters as { description?: string } | undefined)?.description ?? ""), /configured checker modules/);
222
222
 
223
- assert.match(gitCheck.description ?? "", /JSON-first payload/);
223
+ assert.match(gitCheck.description ?? "", /token-optimized JSON payload/);
224
224
  assert.ok(gitCheck.promptGuidelines?.some((line) => line.includes("Behavior contract:")));
225
- assert.match(String((gitCheck.parameters as { description?: string } | undefined)?.description ?? ""), /clean-versus-error repository status/);
225
+ assert.match(String((gitCheck.parameters as { description?: string } | undefined)?.description ?? ""), /aggregate repository status fields/);
226
226
 
227
227
  assert.match(docsCheck.description ?? "", /remediation prompt commands/);
228
228
  assert.ok(docsCheck.promptGuidelines?.some((line) => line.includes("Specialization trigger:")));
@@ -246,18 +246,18 @@ test("replaySessionStart captures active tools, statuses, and cwd semantics", as
246
246
  assert.deepEqual([...report.activeTools].sort(), ["git-path", "static-check"]);
247
247
  assert.equal(report.effectiveCtxCwd, projectBase);
248
248
  assert.equal(report.effectiveProcessCwd, projectBase);
249
- assert.match(
250
- status,
251
- /<accent>git:<\/accent><warning>.*<\/warning><dim> • <\/dim><accent>base:<\/accent><warning>\.<\/warning><dim> • <\/dim><accent>docs:<\/accent><warning>pi-usereq\/docs<\/warning><dim> • <\/dim><accent>tests:<\/accent><warning>tests<\/warning><dim> • <\/dim><accent>src:<\/accent><warning>src<\/warning><dim> • <\/dim><accent>tools:<\/accent><warning>2<\/warning>/,
252
- );
253
- assert.match(
254
- status,
255
- /<accent>context:<\/accent><bg-from-fg-accent><warning>CLEAR<\/warning><\/bg-from-fg-accent>/,
256
- );
257
- assert.match(status, /<accent>elapsed:<\/accent><warning>idle<\/warning>/);
258
- assert.match(status, /<accent>last:<\/accent><warning>N\/A<\/warning>/);
259
- assert.match(status, /<accent>beep:<\/accent><warning>none<\/warning>/);
249
+ const normalizedBasePath = projectBase.split(path.sep).join("/");
250
+ assert.match(status, new RegExp(`<accent>base:</accent><warning>${normalizedBasePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}</warning>`));
251
+ assert.match(status, /<accent>context:<\/accent><bg-from-fg-accent><warning>◀ CLEAR ▶ <\/warning><\/bg-from-fg-accent>/);
252
+ assert.match(status, /<accent>elapsed:<\/accent><warning> ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--<\/warning>/);
253
+ assert.match(status, /<accent>beep:<\/accent><warning>end,esc,err<\/warning>/);
260
254
  assert.match(status, /<accent>sound:<\/accent><warning>none<\/warning>/);
255
+ assert.match(status, /<accent>pushover:<\/accent><warning>off<\/warning>/);
256
+ assert.doesNotMatch(status, /<accent>docs:<\/accent>/);
257
+ assert.doesNotMatch(status, /<accent>src:<\/accent>/);
258
+ assert.doesNotMatch(status, /<accent>tests:<\/accent>/);
259
+ assert.doesNotMatch(status, /<accent>git:<\/accent>/);
260
+ assert.doesNotMatch(status, /<accent>tools:<\/accent>/);
261
261
  assert.equal(report.ui.notifications.length, 0);
262
262
  } finally {
263
263
  fs.rmSync(projectBase, { recursive: true, force: true });
@@ -294,7 +294,8 @@ test("replayCommand captures interactive UI side effects", async () => {
294
294
 
295
295
  assert.deepEqual(report.activeTools, []);
296
296
  assert.ok(report.ui.notifications.some((entry) => entry.message === "Disabled all configurable active tools"));
297
- assert.match(report.ui.statuses["pi-usereq"] ?? "", /<accent>tools:<\/accent><warning>0<\/warning>/);
297
+ assert.doesNotMatch(report.ui.statuses["pi-usereq"] ?? "", /<accent>tools:<\/accent>/);
298
+ assert.match(report.ui.statuses["pi-usereq"] ?? "", /<accent>elapsed:<\/accent><warning> ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--<\/warning>/);
298
299
  } finally {
299
300
  fs.rmSync(projectBase, { recursive: true, force: true });
300
301
  }
@@ -307,9 +308,7 @@ test("replayTool captures tool results and cwd semantics", async () => {
307
308
  const toolResult = report.toolResult as {
308
309
  content?: Array<{ type: string; text?: string }>;
309
310
  details?: {
310
- request: { project_base_path: string };
311
311
  result: { path_value: string; path_present: boolean };
312
- runtime_paths: { git_path: string };
313
312
  execution: { code: number };
314
313
  };
315
314
  };
@@ -317,8 +316,7 @@ test("replayTool captures tool results and cwd semantics", async () => {
317
316
  assert.equal(report.effectiveCtxCwd, projectBase);
318
317
  assert.equal(report.effectiveProcessCwd, projectBase);
319
318
  assert.deepEqual(JSON.parse(toolResult.content?.[0]?.text ?? "{}"), JSON.parse(JSON.stringify(toolResult.details)));
320
- assert.equal(toolResult.details?.request.project_base_path, projectBase);
321
- assert.equal(toolResult.details?.result.path_value, toolResult.details?.runtime_paths.git_path);
319
+ assert.equal(toolResult.details?.result.path_value, projectBase);
322
320
  assert.equal(toolResult.details?.result.path_present, true);
323
321
  assert.equal(toolResult.details?.execution.code, 0);
324
322
  } finally {