pi-usereq 0.12.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.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/pi-usereq/docs/REFERENCES.md +509 -335
- package/pi-usereq/docs/REQUIREMENTS.md +53 -40
- package/pi-usereq/docs/WORKFLOW.md +67 -36
- package/src/core/config.ts +541 -180
- package/src/core/extension-status.ts +2 -2
- package/src/core/path-context.ts +19 -4
- package/src/index.ts +82 -35
- package/tests/attended-results-scenarios.ts +10 -4
- package/tests/cli-command-option-parity.test.ts +28 -10
- package/tests/debug-extension-harness.test.ts +7 -9
- package/tests/extension-registration.test.ts +170 -118
- package/tests/helpers.ts +29 -6
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
createStaticCheckLanguageConfig,
|
|
17
17
|
DEFAULT_DOCS_DIR,
|
|
18
18
|
getDefaultConfig,
|
|
19
|
+
getGlobalConfigPath,
|
|
19
20
|
getProjectConfigPath,
|
|
20
21
|
loadConfig,
|
|
21
22
|
} from "../src/core/config.js";
|
|
@@ -58,7 +59,13 @@ import {
|
|
|
58
59
|
} from "../src/core/extension-status.js";
|
|
59
60
|
import { showPiUsereqSettingsMenu } from "../src/core/settings-menu.js";
|
|
60
61
|
import { getSupportedStaticCheckLanguageSupport } from "../src/core/static-check.js";
|
|
61
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
createTempDir,
|
|
64
|
+
initFixtureRepo,
|
|
65
|
+
readGlobalConfigJson,
|
|
66
|
+
readProjectConfigJson,
|
|
67
|
+
saveFixtureConfigs,
|
|
68
|
+
} from "./helpers.js";
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* @brief Describes one fake extension command registration captured during tests.
|
|
@@ -91,17 +98,16 @@ type RegisteredTool = {
|
|
|
91
98
|
type RegisteredShortcut = { description?: string; handler: (ctx: any) => Promise<void> | void };
|
|
92
99
|
|
|
93
100
|
/**
|
|
94
|
-
* @brief Applies targeted
|
|
95
|
-
* @details Loads the
|
|
101
|
+
* @brief Applies targeted effective-config overrides inside one test project.
|
|
102
|
+
* @details Loads the merged local/global config, merges the supplied top-level overrides, rewrites the split Node config files plus the oracle mirror, and commits the repository-local config artifacts when the fixture is a git repo. Runtime is O(n) in config size. Side effects include filesystem reads, file overwrite, and optional git commits.
|
|
96
103
|
* @param[in] projectBase {string} Fixture project root.
|
|
97
104
|
* @param[in] overrides {Record<string, unknown>} Top-level config overrides.
|
|
98
105
|
* @return {void} No return value.
|
|
99
106
|
*/
|
|
100
107
|
function writeProjectConfigOverrides(projectBase: string, overrides: Record<string, unknown>): void {
|
|
101
|
-
const
|
|
102
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
|
|
108
|
+
const config = loadConfig(projectBase);
|
|
103
109
|
Object.assign(config, overrides);
|
|
104
|
-
|
|
110
|
+
saveFixtureConfigs(projectBase, config);
|
|
105
111
|
if (fs.existsSync(path.join(projectBase, ".git"))) {
|
|
106
112
|
const gitEnv = {
|
|
107
113
|
...process.env,
|
|
@@ -519,13 +525,13 @@ function buildExpectedFakeBasePath(cwd: string): string {
|
|
|
519
525
|
}
|
|
520
526
|
|
|
521
527
|
/**
|
|
522
|
-
* @brief Formats one expected home-relative
|
|
523
|
-
* @details Resolves the supplied
|
|
524
|
-
* @param[in]
|
|
525
|
-
* @return {string} Home-relative or absolute
|
|
528
|
+
* @brief Formats one expected home-relative display path.
|
|
529
|
+
* @details Resolves the supplied path, emits `~` or `~/...` when the path is inside the current user home directory, and otherwise returns the slash-normalized absolute path. Runtime is O(p) in path length. No external state is mutated.
|
|
530
|
+
* @param[in] targetPath {string} Absolute or relative path candidate.
|
|
531
|
+
* @return {string} Home-relative or absolute display path.
|
|
526
532
|
*/
|
|
527
|
-
function
|
|
528
|
-
const resolvedPath = path.resolve(
|
|
533
|
+
function buildExpectedDisplayPath(targetPath: string): string {
|
|
534
|
+
const resolvedPath = path.resolve(targetPath);
|
|
529
535
|
const homePath = path.resolve(process.env.HOME ?? os.homedir());
|
|
530
536
|
if (resolvedPath === homePath) {
|
|
531
537
|
return "~";
|
|
@@ -538,15 +544,32 @@ function buildExpectedNotifyBasePath(cwd: string): string {
|
|
|
538
544
|
}
|
|
539
545
|
|
|
540
546
|
/**
|
|
541
|
-
* @brief Formats
|
|
542
|
-
* @details
|
|
547
|
+
* @brief Formats one expected home-relative base path for notify-template assertions.
|
|
548
|
+
* @details Delegates to the generic display-path formatter so notify-template assertions match the runtime `~`-relative path contract. Runtime is O(p) in path length. No external state is mutated.
|
|
543
549
|
* @param[in] cwd {string} Runtime working directory.
|
|
544
|
-
* @return {string}
|
|
550
|
+
* @return {string} Home-relative or absolute base path used by notification templates.
|
|
545
551
|
*/
|
|
546
|
-
function
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
|
|
552
|
+
function buildExpectedNotifyBasePath(cwd: string): string {
|
|
553
|
+
return buildExpectedDisplayPath(cwd);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* @brief Formats the expected `Show local configuration` path value.
|
|
558
|
+
* @details Resolves the current local project config path and rewrites a leading home-directory prefix to `~` so top-level menu assertions match the documented display-only substitution without changing stored paths. Runtime is O(p) in path length. No external state is mutated.
|
|
559
|
+
* @param[in] cwd {string} Runtime working directory.
|
|
560
|
+
* @return {string} `~`-relative or absolute local config-path display string.
|
|
561
|
+
*/
|
|
562
|
+
function buildExpectedShowLocalConfigPath(cwd: string): string {
|
|
563
|
+
return buildExpectedDisplayPath(getProjectConfigPath(cwd));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* @brief Formats the expected `Show global configuration` path value.
|
|
568
|
+
* @details Resolves the current global config path and rewrites a leading home-directory prefix to `~` so top-level menu assertions match the documented display-only substitution without changing stored paths. Runtime is O(p) in path length. No external state is mutated.
|
|
569
|
+
* @return {string} `~`-relative or absolute global config-path display string.
|
|
570
|
+
*/
|
|
571
|
+
function buildExpectedShowGlobalConfigPath(): string {
|
|
572
|
+
return buildExpectedDisplayPath(getGlobalConfigPath());
|
|
550
573
|
}
|
|
551
574
|
|
|
552
575
|
/**
|
|
@@ -1505,19 +1528,15 @@ test("static-check tools return monolithic text payloads with minimal execution
|
|
|
1505
1528
|
assert.equal(staticCheckResult.details?.execution.code, 0);
|
|
1506
1529
|
assert.deepEqual(staticCheckResult.details?.execution.stderr_lines, undefined);
|
|
1507
1530
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
},
|
|
1518
|
-
}, null, 2)}\n`,
|
|
1519
|
-
"utf8",
|
|
1520
|
-
);
|
|
1531
|
+
saveFixtureConfigs(projectBase, {
|
|
1532
|
+
...getDefaultConfig(projectBase),
|
|
1533
|
+
"docs-dir": DEFAULT_DOCS_DIR,
|
|
1534
|
+
"tests-dir": "tests",
|
|
1535
|
+
"src-dir": ["src"],
|
|
1536
|
+
"static-check": {
|
|
1537
|
+
TypeScript: createStaticCheckLanguageConfig([{ module: "Dummy" }], "disable"),
|
|
1538
|
+
},
|
|
1539
|
+
});
|
|
1521
1540
|
const disabledFilesStaticCheckResult = await executeRegisteredTool(pi, "files-static-check", projectBase, {
|
|
1522
1541
|
files: ["src/check.ts"],
|
|
1523
1542
|
}) as {
|
|
@@ -1541,7 +1560,7 @@ test("configuration menu saves updated docs-dir in project config", async () =>
|
|
|
1541
1560
|
assert.ok(command);
|
|
1542
1561
|
|
|
1543
1562
|
await command!.handler("", createFakeCtx(cwd, { selects: ["docs-dir", "Save and close"], inputs: ["docs/custom"] }));
|
|
1544
|
-
const config =
|
|
1563
|
+
const config = readProjectConfigJson(cwd);
|
|
1545
1564
|
assert.equal(config["docs-dir"], "docs/custom");
|
|
1546
1565
|
});
|
|
1547
1566
|
|
|
@@ -1566,7 +1585,7 @@ test("configuration menu persists trailing-slash-free relative directory values"
|
|
|
1566
1585
|
|
|
1567
1586
|
await command!.handler("", ctx);
|
|
1568
1587
|
|
|
1569
|
-
const config =
|
|
1588
|
+
const config = readProjectConfigJson(cwd);
|
|
1570
1589
|
assert.equal(config["docs-dir"], "docs/custom");
|
|
1571
1590
|
assert.equal(config["tests-dir"], "tests/custom");
|
|
1572
1591
|
assert.deepEqual(config["src-dir"], ["src", "src/custom"]);
|
|
@@ -1586,12 +1605,14 @@ test("configuration menu forces worktree rows off and dim when auto git commit i
|
|
|
1586
1605
|
|
|
1587
1606
|
await command!.handler("", ctx);
|
|
1588
1607
|
|
|
1589
|
-
const
|
|
1608
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
1609
|
+
const globalConfig = readGlobalConfigJson();
|
|
1590
1610
|
const defaultConfig = getDefaultConfig(cwd);
|
|
1591
1611
|
const renderedLockedMenu = (ctx.__state.customRenderLines[1] ?? []).join("\n");
|
|
1592
|
-
assert.equal(
|
|
1593
|
-
assert.equal(
|
|
1594
|
-
assert.equal(
|
|
1612
|
+
assert.equal(globalConfig.AUTO_GIT_COMMIT, "disable");
|
|
1613
|
+
assert.equal(globalConfig.GIT_WORKTREE_ENABLED, "disable");
|
|
1614
|
+
assert.equal(globalConfig.GIT_WORKTREE_PREFIX, defaultConfig.GIT_WORKTREE_PREFIX);
|
|
1615
|
+
assert.equal(Object.hasOwn(localConfig, "AUTO_GIT_COMMIT"), false);
|
|
1595
1616
|
assert.match(renderedLockedMenu, /<dim>Git worktree/);
|
|
1596
1617
|
assert.match(renderedLockedMenu, /<dim>Worktree prefix/);
|
|
1597
1618
|
});
|
|
@@ -1612,7 +1633,7 @@ test("configuration menu omits prompt-delivery controls and persisted config omi
|
|
|
1612
1633
|
assert.equal(fs.existsSync(getProjectConfigPath(cwd)), false);
|
|
1613
1634
|
});
|
|
1614
1635
|
|
|
1615
|
-
test("configuration menus expose
|
|
1636
|
+
test("configuration menus expose local/global config ordering and omit overview or notification-reference rows", async () => {
|
|
1616
1637
|
const cwd = fs.mkdtempSync(
|
|
1617
1638
|
path.join(process.env.HOME ?? process.cwd(), "pi-usereq-menu-structure-"),
|
|
1618
1639
|
);
|
|
@@ -1638,10 +1659,18 @@ test("configuration menus expose show-config ordering and omit overview or notif
|
|
|
1638
1659
|
"Enable tools",
|
|
1639
1660
|
"Notifications",
|
|
1640
1661
|
"Debug",
|
|
1641
|
-
"Show configuration",
|
|
1662
|
+
"Show local configuration",
|
|
1663
|
+
"Show global configuration",
|
|
1642
1664
|
"Reset defaults",
|
|
1643
1665
|
]);
|
|
1644
|
-
assert.
|
|
1666
|
+
assert.match(
|
|
1667
|
+
renderedMenu,
|
|
1668
|
+
new RegExp(`<dim>${buildExpectedShowLocalConfigPath(cwd).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}</dim>`),
|
|
1669
|
+
);
|
|
1670
|
+
assert.match(
|
|
1671
|
+
renderedMenu,
|
|
1672
|
+
new RegExp(`<dim>${buildExpectedShowGlobalConfigPath().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}</dim>`),
|
|
1673
|
+
);
|
|
1645
1674
|
assert.ok(renderedMenu.includes("notification:off • sound:none • pushover:off"));
|
|
1646
1675
|
assert.ok(renderedMenu.includes("disable"));
|
|
1647
1676
|
assert.doesNotMatch(renderedMenu, /beep:/);
|
|
@@ -1668,15 +1697,15 @@ test("configuration menus expose show-config ordering and omit overview or notif
|
|
|
1668
1697
|
]);
|
|
1669
1698
|
});
|
|
1670
1699
|
|
|
1671
|
-
test("show configuration saves pending config, closes the menu, and writes the persisted
|
|
1672
|
-
const cwd = createTempDir("pi-usereq-menu-show-config-");
|
|
1700
|
+
test("show local configuration saves pending config, closes the menu, and writes the persisted local config file text into the editor", async () => {
|
|
1701
|
+
const cwd = createTempDir("pi-usereq-menu-show-local-config-");
|
|
1673
1702
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1674
1703
|
const pi = createFakePi();
|
|
1675
1704
|
piUsereqExtension(pi);
|
|
1676
1705
|
const command = pi.commands.get("pi-usereq");
|
|
1677
1706
|
assert.ok(command);
|
|
1678
1707
|
const ctx = createFakeCtx(cwd, {
|
|
1679
|
-
selects: ["docs-dir", "show-config"],
|
|
1708
|
+
selects: ["docs-dir", "show-local-config"],
|
|
1680
1709
|
inputs: ["docs/custom"],
|
|
1681
1710
|
});
|
|
1682
1711
|
|
|
@@ -1691,6 +1720,28 @@ test("show configuration saves pending config, closes the menu, and writes the p
|
|
|
1691
1720
|
);
|
|
1692
1721
|
});
|
|
1693
1722
|
|
|
1723
|
+
test("show global configuration saves pending config, closes the menu, and writes the persisted global config file text into the editor", async () => {
|
|
1724
|
+
const cwd = createTempDir("pi-usereq-menu-show-global-config-");
|
|
1725
|
+
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1726
|
+
const pi = createFakePi();
|
|
1727
|
+
piUsereqExtension(pi);
|
|
1728
|
+
const command = pi.commands.get("pi-usereq");
|
|
1729
|
+
assert.ok(command);
|
|
1730
|
+
const ctx = createFakeCtx(cwd, {
|
|
1731
|
+
selects: ["Auto git commit", "show-global-config"],
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
await command!.handler("", ctx);
|
|
1735
|
+
|
|
1736
|
+
const persistedText = fs.readFileSync(getGlobalConfigPath(), "utf8");
|
|
1737
|
+
assert.equal(ctx.__state.editorText, persistedText);
|
|
1738
|
+
assert.match(ctx.__state.editorText, /"AUTO_GIT_COMMIT": "disable"/);
|
|
1739
|
+
assert.equal(
|
|
1740
|
+
ctx.__state.selectCalls.filter((call) => call.title === "pi-usereq").length,
|
|
1741
|
+
2,
|
|
1742
|
+
);
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1694
1745
|
test("descendant configuration menus end with Reset defaults only", async () => {
|
|
1695
1746
|
const cwd = createTempDir("pi-usereq-menu-terminal-rows-");
|
|
1696
1747
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
@@ -1785,7 +1836,7 @@ test("debug menu dims locked rows and persists debug settings with focus-preserv
|
|
|
1785
1836
|
await command!.handler("", ctx);
|
|
1786
1837
|
|
|
1787
1838
|
const renderedInitialDebugMenu = (ctx.__state.customRenderLines[1] ?? []).join("\n");
|
|
1788
|
-
const persistedConfig =
|
|
1839
|
+
const persistedConfig = readProjectConfigJson(cwd);
|
|
1789
1840
|
const debugCalls = ctx.__state.selectCalls.filter((call) => call.title === "Debug");
|
|
1790
1841
|
assert.match(renderedInitialDebugMenu, /<dim>Log file/);
|
|
1791
1842
|
assert.match(renderedInitialDebugMenu, /<dim>Log on status/);
|
|
@@ -1811,7 +1862,7 @@ test("debug menu dims locked rows and persists debug settings with focus-preserv
|
|
|
1811
1862
|
selects: ["Debug", "Reset defaults", "Approve reset", "Save and close"],
|
|
1812
1863
|
});
|
|
1813
1864
|
await command!.handler("", resetCtx);
|
|
1814
|
-
const resetConfig =
|
|
1865
|
+
const resetConfig = readProjectConfigJson(cwd);
|
|
1815
1866
|
assert.equal(resetConfig.DEBUG_ENABLED, "disable");
|
|
1816
1867
|
assert.equal(resetConfig.DEBUG_LOG_FILE, DEFAULT_DEBUG_LOG_FILE);
|
|
1817
1868
|
assert.equal(resetConfig.DEBUG_STATUS_CHANGES, "disable");
|
|
@@ -1856,17 +1907,13 @@ test("debug menu rows derive from canonical tool and prompt inventories", async
|
|
|
1856
1907
|
test("tool debug logging honors global enablement and workflow-status filters", async () => {
|
|
1857
1908
|
const idleBase = createTempDir("pi-usereq-tool-debug-idle-");
|
|
1858
1909
|
fs.mkdirSync(path.dirname(getProjectConfigPath(idleBase)), { recursive: true });
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
DEBUG_ENABLED_TOOLS: ["bash"],
|
|
1867
|
-
}, null, 2)}\n`,
|
|
1868
|
-
"utf8",
|
|
1869
|
-
);
|
|
1910
|
+
saveFixtureConfigs(idleBase, {
|
|
1911
|
+
...getDefaultConfig(idleBase),
|
|
1912
|
+
DEBUG_ENABLED: "enable",
|
|
1913
|
+
DEBUG_LOG_FILE: "tool-debug.json",
|
|
1914
|
+
DEBUG_LOG_ON_STATUS: "any",
|
|
1915
|
+
DEBUG_ENABLED_TOOLS: ["bash"],
|
|
1916
|
+
});
|
|
1870
1917
|
const idlePi = createFakePi();
|
|
1871
1918
|
piUsereqExtension(idlePi);
|
|
1872
1919
|
const idleCtx = createFakeCtx(idleBase);
|
|
@@ -1927,17 +1974,13 @@ test("tool debug logging honors global enablement and workflow-status filters",
|
|
|
1927
1974
|
|
|
1928
1975
|
const disabledBase = createTempDir("pi-usereq-tool-debug-disabled-");
|
|
1929
1976
|
fs.mkdirSync(path.dirname(getProjectConfigPath(disabledBase)), { recursive: true });
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
DEBUG_ENABLED_TOOLS: ["bash"],
|
|
1938
|
-
}, null, 2)}\n`,
|
|
1939
|
-
"utf8",
|
|
1940
|
-
);
|
|
1977
|
+
saveFixtureConfigs(disabledBase, {
|
|
1978
|
+
...getDefaultConfig(disabledBase),
|
|
1979
|
+
DEBUG_ENABLED: "disable",
|
|
1980
|
+
DEBUG_LOG_FILE: "tool-debug.json",
|
|
1981
|
+
DEBUG_LOG_ON_STATUS: "any",
|
|
1982
|
+
DEBUG_ENABLED_TOOLS: ["bash"],
|
|
1983
|
+
});
|
|
1941
1984
|
const disabledPi = createFakePi();
|
|
1942
1985
|
piUsereqExtension(disabledPi);
|
|
1943
1986
|
const disabledCtx = createFakeCtx(disabledBase);
|
|
@@ -2181,11 +2224,12 @@ test("notifications reset defaults preserves non-notification settings", async (
|
|
|
2181
2224
|
|
|
2182
2225
|
await command!.handler("", ctx);
|
|
2183
2226
|
|
|
2184
|
-
const
|
|
2185
|
-
|
|
2186
|
-
assert.equal(
|
|
2187
|
-
assert.equal(
|
|
2188
|
-
assert.equal(
|
|
2227
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
2228
|
+
const globalConfig = readGlobalConfigJson();
|
|
2229
|
+
assert.equal(localConfig["docs-dir"], "docs/custom");
|
|
2230
|
+
assert.equal(globalConfig["notify-enabled"], false);
|
|
2231
|
+
assert.equal(globalConfig["notify-sound"], "none");
|
|
2232
|
+
assert.equal(globalConfig["notify-pushover-enabled"], false);
|
|
2189
2233
|
});
|
|
2190
2234
|
|
|
2191
2235
|
test("top-level reset defaults restores all configuration values", async () => {
|
|
@@ -2210,11 +2254,12 @@ test("top-level reset defaults restores all configuration values", async () => {
|
|
|
2210
2254
|
|
|
2211
2255
|
await command!.handler("", ctx);
|
|
2212
2256
|
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
assert.
|
|
2216
|
-
assert.
|
|
2217
|
-
assert.equal(
|
|
2257
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
2258
|
+
const globalConfig = readGlobalConfigJson();
|
|
2259
|
+
assert.equal(localConfig["docs-dir"], DEFAULT_DOCS_DIR);
|
|
2260
|
+
assert.deepEqual(localConfig["src-dir"], ["src"]);
|
|
2261
|
+
assert.equal(globalConfig["notify-enabled"], false);
|
|
2262
|
+
assert.equal(globalConfig["notify-sound"], "none");
|
|
2218
2263
|
});
|
|
2219
2264
|
|
|
2220
2265
|
test("configuration menus reuse CLI settings theme semantics", async () => {
|
|
@@ -4795,17 +4840,11 @@ test("prompt commands remain functional when custom tool registrations are remov
|
|
|
4795
4840
|
test("session_start applies configured pi-usereq startup tools", async () => {
|
|
4796
4841
|
const cwd = createTempDir("pi-usereq-tools-startup-");
|
|
4797
4842
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
"src-dir": ["src", "foobar"],
|
|
4804
|
-
"static-check": {},
|
|
4805
|
-
"enabled-tools": ["files-tokens", "static-check"],
|
|
4806
|
-
}, null, 2)}\n`,
|
|
4807
|
-
"utf8",
|
|
4808
|
-
);
|
|
4843
|
+
saveFixtureConfigs(cwd, {
|
|
4844
|
+
...getDefaultConfig(cwd),
|
|
4845
|
+
"src-dir": ["src", "foobar"],
|
|
4846
|
+
"enabled-tools": ["files-tokens", "static-check"],
|
|
4847
|
+
});
|
|
4809
4848
|
|
|
4810
4849
|
const pi = createFakePi();
|
|
4811
4850
|
piUsereqExtension(pi);
|
|
@@ -4915,7 +4954,7 @@ test("stale replacement contexts do not break workflow-state rendering", () => {
|
|
|
4915
4954
|
const cwd = createTempDir("pi-usereq-stale-render-");
|
|
4916
4955
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
4917
4956
|
const controller = createPiUsereqStatusController();
|
|
4918
|
-
setPiUsereqStatusConfig(controller, getDefaultConfig());
|
|
4957
|
+
setPiUsereqStatusConfig(controller, getDefaultConfig(cwd));
|
|
4919
4958
|
const staleCtx = createFakeCtx(cwd);
|
|
4920
4959
|
Object.defineProperty(staleCtx, "ui", {
|
|
4921
4960
|
configurable: true,
|
|
@@ -5296,7 +5335,9 @@ test("session_start enables default custom tools and default embedded tools only
|
|
|
5296
5335
|
});
|
|
5297
5336
|
|
|
5298
5337
|
test("default configuration applies the documented static-check, debug, notify, sound, and pushover defaults", () => {
|
|
5299
|
-
const
|
|
5338
|
+
const cwd = createTempDir("pi-usereq-default-notify-");
|
|
5339
|
+
const config = getDefaultConfig(cwd);
|
|
5340
|
+
saveFixtureConfigs(cwd, config);
|
|
5300
5341
|
|
|
5301
5342
|
assert.equal(config.AUTO_GIT_COMMIT, "enable");
|
|
5302
5343
|
assert.deepEqual(Object.keys(config["static-check"]).length, 20);
|
|
@@ -5363,6 +5404,21 @@ test("default configuration applies the documented static-check, debug, notify,
|
|
|
5363
5404
|
assert.equal(config["notify-pushover-on-failed"], false);
|
|
5364
5405
|
assert.equal(config.PI_NOTIFY_CMD, 'notify-send -i %%INSTALLATION_PATH%%/resources/images/pi.dev.png -a "PI-useReq" "%%PROMT%% @ %%BASE%% [%%TIME%%]" "%%RESULT%%"');
|
|
5365
5406
|
assert.equal(config["notify-pushover-text"], "%%RESULT%%\n%%ARGS%%");
|
|
5407
|
+
|
|
5408
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
5409
|
+
const globalConfig = readGlobalConfigJson();
|
|
5410
|
+
assert.equal((localConfig["static-check"] as Record<string, any>).Python.enabled, "enable");
|
|
5411
|
+
assert.equal((localConfig["static-check"] as Record<string, any>).Ruby.enabled, "disable");
|
|
5412
|
+
assert.deepEqual((globalConfig["static-check"] as Record<string, any>).Python.checkers, [
|
|
5413
|
+
{ module: "Command", cmd: "pyright", params: ["--outputjson"] },
|
|
5414
|
+
{ module: "Command", cmd: "ruff", params: ["check"] },
|
|
5415
|
+
]);
|
|
5416
|
+
assert.deepEqual((globalConfig["static-check"] as Record<string, any>).Ruby.checkers, []);
|
|
5417
|
+
assert.equal(globalConfig.AUTO_GIT_COMMIT, "enable");
|
|
5418
|
+
assert.equal(globalConfig.GIT_WORKTREE_ENABLED, "enable");
|
|
5419
|
+
assert.equal(globalConfig["notify-enabled"], false);
|
|
5420
|
+
assert.equal(Object.hasOwn(localConfig, "notify-enabled"), false);
|
|
5421
|
+
assert.equal(Object.hasOwn(localConfig, "enabled-tools"), false);
|
|
5366
5422
|
});
|
|
5367
5423
|
|
|
5368
5424
|
test("configuration menu orders tool toggles and can enable embedded builtin tools", async () => {
|
|
@@ -5378,7 +5434,7 @@ test("configuration menu orders tool toggles and can enable embedded builtin too
|
|
|
5378
5434
|
|
|
5379
5435
|
await command!.handler("", ctx);
|
|
5380
5436
|
|
|
5381
|
-
const config =
|
|
5437
|
+
const config = readGlobalConfigJson();
|
|
5382
5438
|
assert.deepEqual(ctx.__state.selectCalls[2]?.items ?? [], [
|
|
5383
5439
|
"compress",
|
|
5384
5440
|
"references",
|
|
@@ -5419,7 +5475,7 @@ test("configuration menu can disable configurable active tools", async () => {
|
|
|
5419
5475
|
}),
|
|
5420
5476
|
);
|
|
5421
5477
|
|
|
5422
|
-
const config =
|
|
5478
|
+
const config = readGlobalConfigJson();
|
|
5423
5479
|
assert.deepEqual(config["enabled-tools"], []);
|
|
5424
5480
|
assert.deepEqual(pi.getActiveTools().filter((toolName: string) => PI_USEREQ_STARTUP_TOOL_NAMES.includes(toolName as never)), []);
|
|
5425
5481
|
});
|
|
@@ -5463,7 +5519,7 @@ test("configuration menu can persist notify and boot-sound settings without chan
|
|
|
5463
5519
|
await pi.emit("session_start", { reason: "startup" }, ctx);
|
|
5464
5520
|
await command!.handler("", ctx);
|
|
5465
5521
|
|
|
5466
|
-
const config =
|
|
5522
|
+
const config = readGlobalConfigJson();
|
|
5467
5523
|
assert.equal(config["notify-enabled"], true);
|
|
5468
5524
|
assert.equal(config["notify-on-completed"], true);
|
|
5469
5525
|
assert.equal(config["notify-on-interrupted"], false);
|
|
@@ -5524,7 +5580,7 @@ test("configuration menu can persist pushover settings", async () => {
|
|
|
5524
5580
|
|
|
5525
5581
|
await command!.handler("", ctx);
|
|
5526
5582
|
|
|
5527
|
-
const config =
|
|
5583
|
+
const config = readGlobalConfigJson();
|
|
5528
5584
|
assert.equal(config["notify-pushover-enabled"], true);
|
|
5529
5585
|
assert.equal(config["notify-pushover-on-completed"], true);
|
|
5530
5586
|
assert.equal(config["notify-pushover-on-interrupted"], false);
|
|
@@ -5551,14 +5607,10 @@ test("configuration menu can persist pushover settings", async () => {
|
|
|
5551
5607
|
test("notifications menu escapes pushover text and locks pushover enablement until credentials exist", async () => {
|
|
5552
5608
|
const cwd = createTempDir("pi-usereq-menu-pushover-display-");
|
|
5553
5609
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
"notify-pushover-text": "first line\nsecond line",
|
|
5559
|
-
}, null, 2)}\n`,
|
|
5560
|
-
"utf8",
|
|
5561
|
-
);
|
|
5610
|
+
saveFixtureConfigs(cwd, {
|
|
5611
|
+
...getDefaultConfig(cwd),
|
|
5612
|
+
"notify-pushover-text": "first line\nsecond line",
|
|
5613
|
+
});
|
|
5562
5614
|
const pi = createFakePi();
|
|
5563
5615
|
piUsereqExtension(pi);
|
|
5564
5616
|
const command = pi.commands.get("pi-usereq");
|
|
@@ -5784,7 +5836,7 @@ test("pushover global enable controls status and suppresses delivery when disabl
|
|
|
5784
5836
|
await command!.handler("", ctx);
|
|
5785
5837
|
writeProjectConfigOverrides(cwd, { GIT_WORKTREE_ENABLED: "disable" });
|
|
5786
5838
|
|
|
5787
|
-
const config =
|
|
5839
|
+
const config = readGlobalConfigJson();
|
|
5788
5840
|
assert.equal(config["notify-pushover-enabled"], false);
|
|
5789
5841
|
assert.equal(config["notify-pushover-on-completed"], true);
|
|
5790
5842
|
assert.equal(
|
|
@@ -6038,8 +6090,9 @@ test("sound routing honors the selected sound state and per-event toggles", asyn
|
|
|
6038
6090
|
test("sound toggle shortcut cycles active runtime pi-notify sound levels without persisting boot config", async () => {
|
|
6039
6091
|
const cwd = createTempDir("pi-usereq-shortcut-notify-");
|
|
6040
6092
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
6041
|
-
const
|
|
6042
|
-
|
|
6093
|
+
const persistedConfig = getDefaultConfig(cwd);
|
|
6094
|
+
saveFixtureConfigs(cwd, persistedConfig);
|
|
6095
|
+
const persistedGlobalConfigText = fs.readFileSync(getGlobalConfigPath(), "utf8");
|
|
6043
6096
|
const previousCwd = process.cwd();
|
|
6044
6097
|
const pi = createFakePi();
|
|
6045
6098
|
process.chdir(cwd);
|
|
@@ -6056,7 +6109,7 @@ test("sound toggle shortcut cycles active runtime pi-notify sound levels without
|
|
|
6056
6109
|
|
|
6057
6110
|
for (const expectedSound of ["low", "mid", "high", "none"]) {
|
|
6058
6111
|
await shortcut!.handler(ctx);
|
|
6059
|
-
assert.equal(fs.readFileSync(
|
|
6112
|
+
assert.equal(fs.readFileSync(getGlobalConfigPath(), "utf8"), persistedGlobalConfigText);
|
|
6060
6113
|
assert.equal(
|
|
6061
6114
|
ctx.__state.statuses.get("pi-usereq"),
|
|
6062
6115
|
buildExpectedFakeStatusText({
|
|
@@ -6077,11 +6130,7 @@ test("session_start loads the active runtime sound from persisted boot config",
|
|
|
6077
6130
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
6078
6131
|
const persistedConfig = getDefaultConfig(cwd);
|
|
6079
6132
|
persistedConfig["notify-sound"] = "mid";
|
|
6080
|
-
|
|
6081
|
-
getProjectConfigPath(cwd),
|
|
6082
|
-
`${JSON.stringify(persistedConfig, null, 2)}\n`,
|
|
6083
|
-
"utf8",
|
|
6084
|
-
);
|
|
6133
|
+
saveFixtureConfigs(cwd, persistedConfig);
|
|
6085
6134
|
const previousCwd = process.cwd();
|
|
6086
6135
|
const pi = createFakePi();
|
|
6087
6136
|
process.chdir(cwd);
|
|
@@ -6180,9 +6229,10 @@ test("configuration menu can toggle per-language static-check enablement", async
|
|
|
6180
6229
|
}),
|
|
6181
6230
|
);
|
|
6182
6231
|
|
|
6183
|
-
const
|
|
6184
|
-
|
|
6185
|
-
assert.
|
|
6232
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
6233
|
+
const globalConfig = readGlobalConfigJson();
|
|
6234
|
+
assert.equal((localConfig["static-check"] as Record<string, any>).Python.enabled, "disable");
|
|
6235
|
+
assert.deepEqual((globalConfig["static-check"] as Record<string, any>).Python.checkers, [
|
|
6186
6236
|
{ module: "Command", cmd: "pyright", params: ["--outputjson"] },
|
|
6187
6237
|
{ module: "Command", cmd: "ruff", params: ["check"] },
|
|
6188
6238
|
]);
|
|
@@ -6210,8 +6260,10 @@ test("configuration menu can add guided static-check entries for explicit suppor
|
|
|
6210
6260
|
}),
|
|
6211
6261
|
);
|
|
6212
6262
|
|
|
6213
|
-
const
|
|
6214
|
-
|
|
6263
|
+
const localConfig = readProjectConfigJson(cwd);
|
|
6264
|
+
const globalConfig = readGlobalConfigJson();
|
|
6265
|
+
assert.equal((localConfig["static-check"] as Record<string, any>).Ruby.enabled, "enable");
|
|
6266
|
+
assert.deepEqual((globalConfig["static-check"] as Record<string, any>).Ruby.checkers, [
|
|
6215
6267
|
{ module: "Command", cmd: "true" },
|
|
6216
|
-
]
|
|
6268
|
+
]);
|
|
6217
6269
|
});
|
package/tests/helpers.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { createRequire } from "node:module";
|
|
|
8
8
|
import {
|
|
9
9
|
DEFAULT_DOCS_DIR,
|
|
10
10
|
getDefaultConfig,
|
|
11
|
+
getGlobalConfigPath,
|
|
11
12
|
getProjectConfigPath,
|
|
12
13
|
saveConfig,
|
|
14
|
+
saveGlobalConfig,
|
|
13
15
|
type StaticCheckLanguageConfig,
|
|
14
16
|
type UseReqConfig,
|
|
15
17
|
} from "../src/core/config.js";
|
|
@@ -31,6 +33,10 @@ const ORACLE_NODE_ROOT = fs.existsSync(path.join(ROOT, "node_modules"))
|
|
|
31
33
|
: path.join(ROOT, "..", "PI-useReq");
|
|
32
34
|
const ORACLE_STUBS_ROOT = path.join(ROOT, "tests", "python_oracle_stubs");
|
|
33
35
|
const FIXTURES_DIR = path.join(ROOT, "tests", "fixtures");
|
|
36
|
+
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), "pi-usereq-home-"));
|
|
37
|
+
process.env.HOME = TEST_HOME;
|
|
38
|
+
process.env.USERPROFILE = TEST_HOME;
|
|
39
|
+
process.env.XDG_CONFIG_HOME = path.join(TEST_HOME, ".config");
|
|
34
40
|
|
|
35
41
|
export function getFixtureFiles(): string[] {
|
|
36
42
|
return fs
|
|
@@ -104,8 +110,16 @@ export function runPythonInline(code: string, cwd = ROOT, envOverrides?: NodeJS.
|
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @brief Creates one temporary working directory and resets the isolated test global config.
|
|
115
|
+
* @details Allocates a unique directory under the system temp root, rewrites the process-scoped test global config file to documented defaults derived from the new project base, and returns the directory path. Runtime is O(n) in serialized config size. Side effects include directory creation and global-config file overwrite under the isolated test home.
|
|
116
|
+
* @param[in] prefix {string} Prefix forwarded to `fs.mkdtempSync(...)`.
|
|
117
|
+
* @return {string} Absolute temporary directory path.
|
|
118
|
+
*/
|
|
107
119
|
export function createTempDir(prefix: string): string {
|
|
108
|
-
|
|
120
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
121
|
+
saveGlobalConfig(getDefaultConfig(tempDir));
|
|
122
|
+
return tempDir;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
/**
|
|
@@ -126,8 +140,8 @@ function writeOracleReqConfig(projectBase: string, config: UseReqConfig): void {
|
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
/**
|
|
129
|
-
* @brief Persists
|
|
130
|
-
* @details Writes `.pi-usereq.json` for the TypeScript
|
|
143
|
+
* @brief Persists Node local/global configs plus the Python-oracle mirror for parity tests.
|
|
144
|
+
* @details Writes split `.pi-usereq.json` and `~/.config/pi-usereq/config.json` files for the TypeScript runtime, then writes `.req/config.json` for the Python oracle using the same effective payload. Runtime is O(n) in serialized config size. Side effects include directory creation and file overwrite.
|
|
131
145
|
* @param[in] projectBase {string} Fixture project root.
|
|
132
146
|
* @param[in] config {UseReqConfig} Effective project configuration to persist.
|
|
133
147
|
* @return {void} No return value.
|
|
@@ -138,15 +152,24 @@ export function saveFixtureConfigs(projectBase: string, config: UseReqConfig): v
|
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
/**
|
|
141
|
-
* @brief Reads the raw persisted TypeScript project config JSON.
|
|
142
|
-
* @details Loads `.pi-usereq.json` without normalization so tests can inspect merge order and unknown metadata preservation exactly as written. Runtime is O(n) in file size. Side effects are limited to filesystem reads.
|
|
155
|
+
* @brief Reads the raw persisted TypeScript local project config JSON.
|
|
156
|
+
* @details Loads `.pi-usereq.json` without normalization so tests can inspect local-scope merge order and unknown metadata preservation exactly as written. Runtime is O(n) in file size. Side effects are limited to filesystem reads.
|
|
143
157
|
* @param[in] projectBase {string} Fixture project root.
|
|
144
|
-
* @return {Record<string, unknown>} Parsed raw configuration object.
|
|
158
|
+
* @return {Record<string, unknown>} Parsed raw local configuration object.
|
|
145
159
|
*/
|
|
146
160
|
export function readProjectConfigJson(projectBase: string): Record<string, unknown> {
|
|
147
161
|
return JSON.parse(fs.readFileSync(getProjectConfigPath(projectBase), "utf8")) as Record<string, unknown>;
|
|
148
162
|
}
|
|
149
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @brief Reads the raw persisted TypeScript global config JSON.
|
|
166
|
+
* @details Loads `~/.config/pi-usereq/config.json` without normalization so tests can inspect global-scope merge order and unknown metadata preservation exactly as written. Runtime is O(n) in file size. Side effects are limited to filesystem reads.
|
|
167
|
+
* @return {Record<string, unknown>} Parsed raw global configuration object.
|
|
168
|
+
*/
|
|
169
|
+
export function readGlobalConfigJson(): Record<string, unknown> {
|
|
170
|
+
return JSON.parse(fs.readFileSync(getGlobalConfigPath(), "utf8")) as Record<string, unknown>;
|
|
171
|
+
}
|
|
172
|
+
|
|
150
173
|
/**
|
|
151
174
|
* @brief Writes one executable shell script for integration tests.
|
|
152
175
|
* @details Creates the parent directory when required, writes UTF-8 content, and marks the file owner-executable. Runtime is O(n) in script size. Side effects include directory creation, file overwrite, and permission changes.
|