pi-usereq 0.11.0 → 0.13.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 (53) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +6 -6
  3. package/package.json +1 -1
  4. package/pi-usereq/docs/REFERENCES.md +1135 -834
  5. package/pi-usereq/docs/REQUIREMENTS.md +177 -110
  6. package/pi-usereq/docs/WORKFLOW.md +244 -79
  7. package/scripts/lib/extension-debug-harness.ts +2 -2
  8. package/scripts/tool-args-to-params.ts +2 -2
  9. package/src/cli.ts +12 -12
  10. package/src/core/config.ts +541 -180
  11. package/src/core/extension-status.ts +69 -12
  12. package/src/core/path-context.ts +19 -4
  13. package/src/core/pi-notify.ts +5 -5
  14. package/src/core/pi-usereq-tools.ts +4 -2
  15. package/src/core/prompt-command-catalog.ts +4 -5
  16. package/src/core/prompt-command-runtime.ts +183 -44
  17. package/src/core/prompts.ts +0 -2
  18. package/src/core/req-references-command.ts +175 -0
  19. package/src/core/req-reset-command.ts +323 -0
  20. package/src/core/resources.ts +6 -23
  21. package/src/core/settings-menu.ts +85 -28
  22. package/src/core/tool-runner.ts +26 -6
  23. package/src/index.ts +601 -116
  24. package/tests/attended-results-scenarios.ts +15 -9
  25. package/tests/cli-command-option-parity.test.ts +53 -35
  26. package/tests/debug-extension-harness.test.ts +8 -10
  27. package/tests/extension-registration.test.ts +1204 -205
  28. package/tests/helpers.ts +29 -6
  29. package/tests/oracle-project.test.ts +4 -4
  30. package/tests/oracle-standalone.test.ts +5 -5
  31. package/src/core/reference-payload.ts +0 -752
  32. package/src/resources/prompts/references.md +0 -64
  33. /package/tests/fixtures_attended_results/project/{references.json → summarize.json} +0 -0
  34. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_c.c.json +0 -0
  35. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_cpp.cpp.json +0 -0
  36. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_csharp.cs.json +0 -0
  37. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_elixir.ex.json +0 -0
  38. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_go.go.json +0 -0
  39. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_haskell.hs.json +0 -0
  40. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_java.java.json +0 -0
  41. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_javascript.js.json +0 -0
  42. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_kotlin.kt.json +0 -0
  43. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_lua.lua.json +0 -0
  44. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_perl.pl.json +0 -0
  45. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_php.php.json +0 -0
  46. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_python.py.json +0 -0
  47. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_ruby.rb.json +0 -0
  48. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_rust.rs.json +0 -0
  49. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_scala.scala.json +0 -0
  50. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_shell.sh.json +0 -0
  51. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_swift.swift.json +0 -0
  52. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_typescript.ts.json +0 -0
  53. /package/tests/fixtures_attended_results/standalone/{files-references → files-summarize}/fixture_zig.zig.json +0 -0
@@ -14,11 +14,13 @@ import { detectLanguage } from "../src/core/generate-markdown.js";
14
14
  import { LANGUAGE_TAGS } from "../src/core/find-constructs.js";
15
15
  import {
16
16
  createStaticCheckLanguageConfig,
17
- getProjectConfigPath,
17
+ getDefaultConfig,
18
18
  type UseReqConfig,
19
19
  } from "../src/core/config.js";
20
20
  import {
21
21
  initFixtureRepo,
22
+ readGlobalConfigJson,
23
+ readProjectConfigJson,
22
24
  runNodeCli,
23
25
  runPythonCli,
24
26
  runPythonInline,
@@ -312,9 +314,9 @@ function buildStandaloneScenarios(): AttendedScenario[] {
312
314
  ["--files-tokens", relativeFixturePath],
313
315
  ),
314
316
  createStandaloneScenario(
315
- `standalone/files-references/${fixtureName}`,
316
- path.posix.join("standalone", "files-references", `${fixtureName}.json`),
317
- ["--files-references", relativeFixturePath],
317
+ `standalone/files-summarize/${fixtureName}`,
318
+ path.posix.join("standalone", "files-summarize", `${fixtureName}.json`),
319
+ ["--files-summarize", relativeFixturePath],
318
320
  ),
319
321
  createStandaloneScenario(
320
322
  `standalone/files-compress/${fixtureName}`,
@@ -369,10 +371,10 @@ function getFailingStaticCheckConfig(): UseReqConfig["static-check"] {
369
371
  */
370
372
  function buildProjectScenarios(): AttendedScenario[] {
371
373
  return [
372
- createProjectScenario("project/references", path.posix.join("project", "references.json"), () => {
374
+ createProjectScenario("project/summarize", path.posix.join("project", "summarize.json"), () => {
373
375
  const { projectBase } = initFixtureRepo();
374
376
  return {
375
- args: ["--references"],
377
+ args: ["--summarize"],
376
378
  cwd: projectBase,
377
379
  normalize: createProjectNormalizer(projectBase),
378
380
  cleanup: () => removePath(projectBase),
@@ -450,10 +452,14 @@ function buildProjectScenarios(): AttendedScenario[] {
450
452
  normalize: createProjectNormalizer(projectBase),
451
453
  cleanup: () => removePath(projectBase),
452
454
  postAssert: () => {
453
- const payload = JSON.parse(fs.readFileSync(getProjectConfigPath(projectBase), "utf8")) as UseReqConfig;
454
- assert.deepEqual(payload["static-check"].Python, createStaticCheckLanguageConfig([
455
+ const defaultConfig = getDefaultConfig(projectBase);
456
+ const localPayload = readProjectConfigJson(projectBase) as unknown as Record<string, unknown>;
457
+ const globalPayload = readGlobalConfigJson() as unknown as Record<string, unknown>;
458
+ assert.equal((localPayload["static-check"] as Record<string, any>).Python.enabled, "enable");
459
+ assert.deepEqual((globalPayload["static-check"] as Record<string, any>).Python.checkers, [
460
+ ...defaultConfig["static-check"].Python.checkers,
455
461
  { module: "Command", cmd: "git", params: ["--version"] },
456
- ], "enable"));
462
+ ]);
457
463
  },
458
464
  };
459
465
  },
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
12
12
  import {
13
13
  createStaticCheckLanguageConfig,
14
14
  DEFAULT_DOCS_DIR,
15
+ getDefaultConfig,
15
16
  getProjectConfigPath,
16
17
  type UseReqConfig,
17
18
  } from "../src/core/config.js";
@@ -21,6 +22,7 @@ import {
21
22
  createTempDir,
22
23
  getFixtureFiles,
23
24
  initFixtureRepo,
25
+ readGlobalConfigJson,
24
26
  readProjectConfigJson,
25
27
  runNodeCli,
26
28
  runPythonCli,
@@ -115,11 +117,11 @@ function getFixtureTagFilter(filePath: string): string {
115
117
  }
116
118
 
117
119
  /**
118
- * @brief Returns the persisted per-language static-check config for one language.
119
- * @details Reads the raw project config JSON so tests can assert entry order, duplicate suppression, enable-flag persistence, and metadata preservation exactly as written. Runtime is O(n) in config file size. Side effects are limited to filesystem reads.
120
+ * @brief Returns the persisted local static-check config for one language.
121
+ * @details Reads the raw local project config JSON so tests can assert enable-flag persistence and local-scope shape exactly as written. Runtime is O(n) in config file size. Side effects are limited to filesystem reads.
120
122
  * @param[in] projectBase {string} Fixture project root.
121
123
  * @param[in] language {string} Canonical language key.
122
- * @return {Record<string, unknown> | undefined} Persisted language config object when present.
124
+ * @return {Record<string, unknown> | undefined} Persisted local language config object when present.
123
125
  */
124
126
  function getStaticCheckLanguageConfig(projectBase: string, language: string): Record<string, unknown> | undefined {
125
127
  const payload = readProjectConfigJson(projectBase);
@@ -128,14 +130,17 @@ function getStaticCheckLanguageConfig(projectBase: string, language: string): Re
128
130
  }
129
131
 
130
132
  /**
131
- * @brief Returns the persisted static-check entries for one language.
132
- * @details Reads the raw project config JSON and extracts the `checkers` array so tests can assert entry order and duplicate suppression exactly as written. Runtime is O(n) in config file size. Side effects are limited to filesystem reads.
133
- * @param[in] projectBase {string} Fixture project root.
133
+ * @brief Returns the persisted global static-check entries for one language.
134
+ * @details Reads the raw global config JSON and extracts the `checkers` array so tests can assert entry order and duplicate suppression exactly as written. Runtime is O(n) in config file size. Side effects are limited to filesystem reads.
135
+ * @param[in] projectBase {string} Fixture project root retained for stable call-site shape.
134
136
  * @param[in] language {string} Canonical language key.
135
- * @return {Array<Record<string, unknown>>} Persisted checker-entry array or an empty array.
137
+ * @return {Array<Record<string, unknown>>} Persisted global checker-entry array or an empty array.
136
138
  */
137
139
  function getStaticCheckEntries(projectBase: string, language: string): Array<Record<string, unknown>> {
138
- const languageConfig = getStaticCheckLanguageConfig(projectBase, language);
140
+ void projectBase;
141
+ const payload = readGlobalConfigJson();
142
+ const staticCheck = (payload["static-check"] ?? {}) as Record<string, Record<string, unknown>>;
143
+ const languageConfig = staticCheck[language];
139
144
  return Array.isArray(languageConfig?.checkers)
140
145
  ? languageConfig.checkers as Array<Record<string, unknown>>
141
146
  : [];
@@ -171,18 +176,18 @@ const IMPORTED_CASE_MAPPINGS: ImportedCaseMapping[] = [
171
176
  { sourceId: "test_files_commands.py::TestFilesTokensCommand::test_files_tokens_no_base_required", targetId: "oracle::standalone::files-tokens-explicit-files" },
172
177
  { sourceId: "test_files_commands.py::TestFilesTokensCommand::test_files_tokens_skip_missing", targetId: "oracle::standalone::files-tokens-explicit-files" },
173
178
  { sourceId: "test_files_commands.py::TestFilesTokensCommand::test_files_tokens_all_missing_errors", targetId: "oracle::standalone::files-tokens-explicit-files" },
174
- { sourceId: "test_files_commands.py::TestFilesReferencesCommand::test_files_references_basic", targetId: "direct::files-references-explicit-files" },
175
- { sourceId: "test_files_commands.py::TestFilesReferencesCommand::test_files_references_no_base_required", targetId: "direct::files-references-explicit-files" },
176
- { sourceId: "test_files_commands.py::TestFilesReferencesCommand::test_files_references_verbose_outputs_progress", targetId: "direct::files-references-verbose" },
179
+ { sourceId: "test_files_commands.py::TestFilesSummarizeCommand::test_files_summarize_basic", targetId: "direct::files-summarize-explicit-files" },
180
+ { sourceId: "test_files_commands.py::TestFilesSummarizeCommand::test_files_summarize_no_base_required", targetId: "direct::files-summarize-explicit-files" },
181
+ { sourceId: "test_files_commands.py::TestFilesSummarizeCommand::test_files_summarize_verbose_outputs_progress", targetId: "direct::files-summarize-verbose" },
177
182
  { sourceId: "test_files_commands.py::TestFilesCompressCommand::test_files_compress_basic", targetId: "oracle::standalone::files-compress-line-number-modes" },
178
183
  { sourceId: "test_files_commands.py::TestFilesCompressCommand::test_files_compress_no_base_required", targetId: "oracle::standalone::files-compress-line-number-modes" },
179
184
  { sourceId: "test_files_commands.py::TestFilesCompressCommand::test_files_compress_no_line_numbers_by_default", targetId: "oracle::standalone::files-compress-line-number-modes" },
180
185
  { sourceId: "test_files_commands.py::TestFilesCompressCommand::test_files_compress_enable_line_numbers", targetId: "oracle::standalone::files-compress-line-number-modes" },
181
186
  { sourceId: "test_files_commands.py::TestFilesCompressCommand::test_files_compress_verbose_outputs_progress", targetId: "oracle::standalone::files-compress-verbose" },
182
- { sourceId: "test_files_commands.py::TestReferencesCommand::test_rejects_base", targetId: "direct::references-modes" },
183
- { sourceId: "test_files_commands.py::TestReferencesCommand::test_references_with_here", targetId: "direct::references-modes" },
184
- { sourceId: "test_files_commands.py::TestReferencesCommand::test_references_prepends_files_structure", targetId: "direct::references-modes" },
185
- { sourceId: "test_files_commands.py::TestReferencesCommand::test_references_from_config", targetId: "direct::references-modes" },
187
+ { sourceId: "test_files_commands.py::TestSummarizeCommand::test_rejects_base", targetId: "direct::summarize-modes" },
188
+ { sourceId: "test_files_commands.py::TestSummarizeCommand::test_summarize_with_here", targetId: "direct::summarize-modes" },
189
+ { sourceId: "test_files_commands.py::TestSummarizeCommand::test_summarize_prepends_files_structure", targetId: "direct::summarize-modes" },
190
+ { sourceId: "test_files_commands.py::TestSummarizeCommand::test_summarize_from_config", targetId: "direct::summarize-modes" },
186
191
  { sourceId: "test_files_commands.py::TestCompressCommand::test_rejects_base", targetId: "oracle::project::compress-modes" },
187
192
  { sourceId: "test_files_commands.py::TestCompressCommand::test_compress_with_here", targetId: "oracle::project::compress-modes" },
188
193
  { sourceId: "test_files_commands.py::TestCompressCommand::test_compress_no_line_numbers_by_default", targetId: "oracle::project::compress-modes" },
@@ -254,7 +259,7 @@ const TARGET_CASES: TargetParityCase[] = [
254
259
  },
255
260
  },
256
261
  {
257
- id: "direct::files-references-explicit-files",
262
+ id: "direct::files-summarize-explicit-files",
258
263
  run(t) {
259
264
  const filePath = createScratchFile(
260
265
  t,
@@ -262,8 +267,8 @@ const TARGET_CASES: TargetParityCase[] = [
262
267
  "/**\n * @file\n * @brief Sample file.\n */\n\n/**\n * @brief Build sample value.\n * @param[in] input {string} Caller text.\n * @return {string} Upper-cased text.\n */\nexport function buildSample(input: string): string {\n return input.toUpperCase();\n}\n",
263
268
  );
264
269
  const cwd = path.dirname(filePath);
265
- const node = runNodeCli(["--files-references", filePath], cwd);
266
- const python = runPythonCli(["--files-references", filePath], cwd);
270
+ const node = runNodeCli(["--files-summarize", filePath], cwd);
271
+ const python = runPythonCli(["--files-summarize", filePath], cwd);
267
272
  assert.equal(node.status, python.status);
268
273
  assert.equal(node.stdout, python.stdout);
269
274
  assert.equal(node.stderr, python.stderr);
@@ -273,12 +278,12 @@ const TARGET_CASES: TargetParityCase[] = [
273
278
  },
274
279
  },
275
280
  {
276
- id: "direct::files-references-verbose",
281
+ id: "direct::files-summarize-verbose",
277
282
  run(t) {
278
283
  const filePath = createScratchFile(t, "sample.py", "value = 1\n");
279
284
  const cwd = path.dirname(filePath);
280
- const node = runNodeCli(["--verbose", "--files-references", filePath], cwd);
281
- const python = runPythonCli(["--verbose", "--files-references", filePath], cwd);
285
+ const node = runNodeCli(["--verbose", "--files-summarize", filePath], cwd);
286
+ const python = runPythonCli(["--verbose", "--files-summarize", filePath], cwd);
282
287
  assert.equal(node.status, python.status);
283
288
  assert.equal(node.stdout, python.stdout);
284
289
  assert.equal(node.stderr, python.stderr);
@@ -302,15 +307,15 @@ const TARGET_CASES: TargetParityCase[] = [
302
307
  },
303
308
  },
304
309
  {
305
- id: "direct::references-modes",
310
+ id: "direct::summarize-modes",
306
311
  run(t) {
307
312
  const { projectBase } = createFixtureRepo(t, { fixtures: [] });
308
313
  fs.writeFileSync(path.join(projectBase, "src", "alpha.ts"), "export const ALPHA = 1;\n", "utf8");
309
314
  fs.mkdirSync(path.join(projectBase, "src", "nested"), { recursive: true });
310
315
  fs.writeFileSync(path.join(projectBase, "src", "nested", "beta.ts"), "export function beta(): number {\n return 2;\n}\n", "utf8");
311
316
 
312
- const directNode = runNodeCli(["--references"], projectBase);
313
- const directPython = runPythonCli(["--references"], projectBase);
317
+ const directNode = runNodeCli(["--summarize"], projectBase);
318
+ const directPython = runPythonCli(["--summarize"], projectBase);
314
319
  assert.equal(directNode.status, directPython.status);
315
320
  assert.equal(directNode.stdout, directPython.stdout);
316
321
  assert.equal(directNode.stderr, directPython.stderr);
@@ -318,13 +323,13 @@ const TARGET_CASES: TargetParityCase[] = [
318
323
  assert.match(directNode.stdout, /src\/nested\/beta\.ts/);
319
324
  assert.match(directNode.stdout, /# beta\.ts \| TypeScript/);
320
325
 
321
- const hereNode = runNodeCli(["--here", "--references"], projectBase);
322
- const herePython = runPythonCli(["--here", "--references"], projectBase);
326
+ const hereNode = runNodeCli(["--here", "--summarize"], projectBase);
327
+ const herePython = runPythonCli(["--here", "--summarize"], projectBase);
323
328
  assert.equal(hereNode.status, herePython.status);
324
329
  assert.equal(hereNode.stdout, herePython.stdout);
325
330
  assert.equal(hereNode.stderr, herePython.stderr);
326
331
 
327
- const baseRejected = runNodeCli(["--base", projectBase, "--references"], projectBase);
332
+ const baseRejected = runNodeCli(["--base", projectBase, "--summarize"], projectBase);
328
333
  assert.notEqual(baseRejected.status, 0);
329
334
  assert.match(baseRejected.stderr, /do not allow --base/);
330
335
  },
@@ -594,6 +599,7 @@ const TARGET_CASES: TargetParityCase[] = [
594
599
  fs.mkdirSync(path.join(projectBase, "src"), { recursive: true });
595
600
  fs.mkdirSync(path.join(projectBase, "tests"), { recursive: true });
596
601
  fs.mkdirSync(path.join(projectBase, ...DEFAULT_DOCS_DIR.split("/")), { recursive: true });
602
+ const originalGlobalConfig = structuredClone(readGlobalConfigJson());
597
603
  const result = runNodeCli(
598
604
  ["--base", projectBase, "--enable-static-check", "C=Command,nonexistent_tool_xyz_12345"],
599
605
  projectBase,
@@ -601,6 +607,7 @@ const TARGET_CASES: TargetParityCase[] = [
601
607
  assert.equal(result.status, 1);
602
608
  assert.match(result.stderr, /not an executable program/);
603
609
  assert.ok(!fs.existsSync(getProjectConfigPath(projectBase)));
610
+ assert.deepEqual(readGlobalConfigJson(), originalGlobalConfig);
604
611
 
605
612
  const removedModule = runNodeCli(
606
613
  ["--base", projectBase, "--enable-static-check", "Python=Ruff"],
@@ -609,6 +616,7 @@ const TARGET_CASES: TargetParityCase[] = [
609
616
  assert.equal(removedModule.status, 1);
610
617
  assert.match(removedModule.stderr, /unknown module/i);
611
618
  assert.ok(!fs.existsSync(getProjectConfigPath(projectBase)));
619
+ assert.deepEqual(readGlobalConfigJson(), originalGlobalConfig);
612
620
  },
613
621
  },
614
622
  {
@@ -629,13 +637,18 @@ const TARGET_CASES: TargetParityCase[] = [
629
637
  projectBase,
630
638
  );
631
639
  assert.equal(persisted.status, 0, persisted.stderr);
640
+ const defaultConfig = getDefaultConfig(projectBase);
632
641
  assert.equal(getStaticCheckLanguageConfig(projectBase, "Python")?.enabled, "enable");
633
642
  assert.equal(getStaticCheckLanguageConfig(projectBase, "C")?.enabled, "enable");
634
643
  assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [
644
+ ...defaultConfig["static-check"].Python.checkers,
635
645
  { module: "Command", cmd: "git", params: ["--version"] },
636
646
  { module: "Command", cmd: "git", params: ["--help"] },
637
647
  ]);
638
- assert.deepEqual(getStaticCheckEntries(projectBase, "C"), [{ module: "Command", cmd: "git", params: ["--version"] }]);
648
+ assert.deepEqual(getStaticCheckEntries(projectBase, "C"), [
649
+ ...defaultConfig["static-check"].C.checkers,
650
+ { module: "Command", cmd: "git", params: ["--version"] },
651
+ ]);
639
652
  },
640
653
  },
641
654
  {
@@ -658,10 +671,15 @@ const TARGET_CASES: TargetParityCase[] = [
658
671
  projectBase,
659
672
  );
660
673
  assert.equal(result.status, 0, result.stderr);
674
+ const defaultConfig = getDefaultConfig(projectBase);
661
675
  assert.equal(getStaticCheckLanguageConfig(projectBase, "Python")?.enabled, "enable");
662
676
  assert.equal(getStaticCheckLanguageConfig(projectBase, "C")?.enabled, "enable");
663
- assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [{ module: "Command", cmd: "git", params: ["--version"] }]);
677
+ assert.deepEqual(getStaticCheckEntries(projectBase, "Python"), [
678
+ ...defaultConfig["static-check"].Python.checkers,
679
+ { module: "Command", cmd: "git", params: ["--version"] },
680
+ ]);
664
681
  assert.deepEqual(getStaticCheckEntries(projectBase, "C"), [
682
+ ...defaultConfig["static-check"].C.checkers,
665
683
  { module: "Command", cmd: "git", params: ["--version"] },
666
684
  { module: "Command", cmd: "git", params: ["--help"] },
667
685
  ]);
@@ -709,7 +727,7 @@ const TARGET_CASES: TargetParityCase[] = [
709
727
  },
710
728
  },
711
729
  {
712
- id: "direct::fixtures::files-references-fixture-shape",
730
+ id: "direct::fixtures::files-summarize-fixture-shape",
713
731
  async run(t) {
714
732
  for (const fixture of getFixtureFiles()) {
715
733
  if (path.basename(fixture) === TAB_SENSITIVE_FIXTURE_NAME) {
@@ -717,8 +735,8 @@ const TARGET_CASES: TargetParityCase[] = [
717
735
  }
718
736
  await t.test(path.basename(fixture), () => {
719
737
  const cwd = path.dirname(fixture);
720
- const node = runNodeCli(["--files-references", fixture], cwd);
721
- const python = runPythonCli(["--files-references", fixture], cwd);
738
+ const node = runNodeCli(["--files-summarize", fixture], cwd);
739
+ const python = runPythonCli(["--files-summarize", fixture], cwd);
722
740
  assert.equal(node.status, python.status);
723
741
  assert.equal(node.stdout, python.stdout);
724
742
  assert.equal(node.stderr, python.stderr);
@@ -748,9 +766,9 @@ const TARGET_CASES: TargetParityCase[] = [
748
766
  assert.ok(fixture, `missing ${TAB_SENSITIVE_FIXTURE_NAME}`);
749
767
  const cwd = path.dirname(fixture);
750
768
 
751
- const references = runNodeCli(["--files-references", fixture], cwd);
752
- assert.equal(references.status, 0, references.stderr);
753
- assert.match(references.stdout, /\tvar wg sync\.WaitGroup/);
769
+ const summarize = runNodeCli(["--files-summarize", fixture], cwd);
770
+ assert.equal(summarize.status, 0, summarize.stderr);
771
+ assert.match(summarize.stdout, /\tvar wg sync\.WaitGroup/);
754
772
 
755
773
  const compress = runNodeCli(["--files-compress", fixture, "--enable-line-numbers"], cwd);
756
774
  assert.equal(compress.status, 0, compress.stderr);
@@ -12,22 +12,21 @@ import {
12
12
  type OfflineContractSnapshot,
13
13
  } from "../scripts/lib/extension-debug-harness.js";
14
14
  import { buildParityReport, type SdkContractSnapshot } from "../scripts/lib/sdk-smoke.js";
15
- import { getProjectConfigPath } from "../src/core/config.js";
15
+ import { loadConfig } from "../src/core/config.js";
16
16
  import { PI_USEREQ_STATUS_HOOK_NAMES } from "../src/core/extension-status.js";
17
- import { initFixtureRepo } from "./helpers.js";
17
+ import { initFixtureRepo, saveFixtureConfigs } from "./helpers.js";
18
18
 
19
19
  /**
20
20
  * @brief Persists a targeted enabled-tool list into a fixture project config.
21
- * @details Loads `.pi-usereq.json`, replaces the `enabled-tools` array with the supplied values, and writes the updated JSON back to disk with a trailing newline. Runtime is O(n) in config size. Side effects include filesystem reads and file overwrite.
21
+ * @details Loads the effective split config, replaces the global `enabled-tools` array with the supplied values, and rewrites the local/global plus oracle mirror files so offline harness tests observe the same runtime state. Runtime is O(n) in config size. Side effects include filesystem reads and file overwrite.
22
22
  * @param[in] projectBase {string} Fixture project root.
23
23
  * @param[in] enabledTools {string[]} Enabled-tool names to persist.
24
24
  * @return {void} No return value.
25
25
  */
26
26
  function writeEnabledTools(projectBase: string, enabledTools: string[]): void {
27
- const configPath = getProjectConfigPath(projectBase);
28
- const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
27
+ const config = loadConfig(projectBase);
29
28
  config["enabled-tools"] = enabledTools;
30
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
29
+ saveFixtureConfigs(projectBase, config);
31
30
  }
32
31
 
33
32
  /**
@@ -237,7 +236,7 @@ test("replaySessionStart captures active tools, statuses, and cwd semantics", as
237
236
  assert.match(status, /<accent>status:<\/accent><warning>idle<\/warning>/);
238
237
  assert.match(status, new RegExp(`<accent>branch:<\\/accent><warning>${escapedBranch}<\\/warning>`));
239
238
  assert.doesNotMatch(status, /<accent>current-path:<\/accent>/);
240
- assert.match(status, /<accent>context:<\/accent>▕_▏/);
239
+ assert.match(status, /<accent>context:<\/accent><warning>▕_▏<\/warning>/);
241
240
  assert.match(status, /<accent>elapsed:<\/accent><warning>⏱︎ --:-- ⚑ --:-- ⌛︎--:--<\/warning>/);
242
241
  assert.match(status, /<accent>sound:<\/accent><warning>none<\/warning>/);
243
242
  assert.doesNotMatch(status, /<accent>beep:<\/accent>/);
@@ -257,10 +256,9 @@ test("replaySessionStart captures active tools, statuses, and cwd semantics", as
257
256
  test("replayCommand captures prompt command payloads", async () => {
258
257
  const { projectBase } = initFixtureRepo();
259
258
  try {
260
- const configPath = getProjectConfigPath(projectBase);
261
- const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
259
+ const config = loadConfig(projectBase);
262
260
  config.GIT_WORKTREE_ENABLED = "disable";
263
- fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
261
+ saveFixtureConfigs(projectBase, config);
264
262
  assert.equal(spawnSync("git", ["add", ".pi-usereq.json", ".req/config.json"], {
265
263
  cwd: projectBase,
266
264
  encoding: "utf8",