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.
- package/.gitignore +3 -0
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/{req → pi-usereq}/docs/REFERENCES.md +196 -159
- package/{req → pi-usereq}/docs/REQUIREMENTS.md +65 -44
- package/{req → pi-usereq}/docs/WORKFLOW.md +66 -73
- package/src/core/config.ts +39 -12
- package/src/core/extension-status.ts +94 -58
- package/src/core/pi-notify.ts +198 -11
- package/src/core/runtime-project-paths.ts +3 -32
- package/src/core/settings-menu.ts +9 -4
- package/src/core/static-check.ts +35 -171
- package/src/index.ts +281 -86
- package/tests/attended-results-scenarios.ts +3 -13
- package/tests/cli-command-option-parity.test.ts +21 -12
- package/tests/debug-extension-harness.test.ts +11 -7
- package/tests/extension-registration.test.ts +371 -71
- package/tests/oracle-project.test.ts +8 -3
- package/tests/oracle-standalone.test.ts +7 -13
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_c.c.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_cpp.cpp.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_csharp.cs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_elixir.ex.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_go.go.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_haskell.hs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_java.java.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_javascript.js.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_kotlin.kt.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_lua.lua.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_perl.pl.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_php.php.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_python.py.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_ruby.rb.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_rust.rs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_scala.scala.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_shell.sh.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_swift.swift.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_typescript.ts.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-pylance/fixture_zig.zig.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_c.c.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_cpp.cpp.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_csharp.cs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_elixir.ex.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_go.go.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_haskell.hs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_java.java.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_javascript.js.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_kotlin.kt.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_lua.lua.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_perl.pl.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_php.php.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_python.py.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_ruby.rb.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_rust.rs.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_scala.scala.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_shell.sh.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_swift.swift.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_typescript.ts.json +0 -5
- package/tests/fixtures_attended_results/standalone/test-static-check-ruff/fixture_zig.zig.json +0 -5
|
@@ -4,7 +4,13 @@ import { spawnSync } from "node:child_process";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import piUsereqExtension from "../src/index.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_DOCS_DIR,
|
|
9
|
+
getDefaultConfig,
|
|
10
|
+
getProjectConfigPath,
|
|
11
|
+
} from "../src/core/config.js";
|
|
12
|
+
import { formatRuntimePathForDisplay } from "../src/core/path-context.js";
|
|
13
|
+
import { setPiNotifyHttpsRequestForTests } from "../src/core/pi-notify.js";
|
|
8
14
|
import {
|
|
9
15
|
PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES,
|
|
10
16
|
PI_USEREQ_EMBEDDED_TOOL_NAMES,
|
|
@@ -284,9 +290,9 @@ function formatFakeThemeBackgroundFromForeground(color: string, text: string): s
|
|
|
284
290
|
|
|
285
291
|
/**
|
|
286
292
|
* @brief Builds the expected fake context-bar payload for assertions.
|
|
287
|
-
* @details Resolves the empty-state
|
|
288
|
-
* non-positive percent, resolves the
|
|
289
|
-
* above 90, and otherwise reconstructs the
|
|
293
|
+
* @details Resolves the empty-state `◀ CLEAR ▶ ` overlay for unavailable or
|
|
294
|
+
* non-positive percent, resolves the centered ` ◀ FULL ▶ ` overlay for percent
|
|
295
|
+
* above 90, and otherwise reconstructs the 10-cell bar. Runtime is O(1). No
|
|
290
296
|
* external state is mutated.
|
|
291
297
|
* @param[in] options {{ filledCells: number; percent?: number | null }} Expected context-bar facts.
|
|
292
298
|
* @return {string} Encoded context-bar string.
|
|
@@ -298,16 +304,16 @@ function buildExpectedFakeContextBar(options: {
|
|
|
298
304
|
if (options.percent === undefined || options.percent === null || options.percent <= 0) {
|
|
299
305
|
return formatFakeThemeBackgroundFromForeground(
|
|
300
306
|
"accent",
|
|
301
|
-
formatFakeThemeForeground("warning", "CLEAR"),
|
|
307
|
+
formatFakeThemeForeground("warning", "◀ CLEAR ▶ "),
|
|
302
308
|
);
|
|
303
309
|
}
|
|
304
310
|
if (options.percent > 90) {
|
|
305
311
|
return formatFakeThemeBackgroundFromForeground(
|
|
306
312
|
"warning",
|
|
307
|
-
formatFakeThemeForeground("error", "FULL
|
|
313
|
+
formatFakeThemeForeground("error", " ◀ FULL ▶ "),
|
|
308
314
|
);
|
|
309
315
|
}
|
|
310
|
-
return Array.from({ length:
|
|
316
|
+
return Array.from({ length: 10 }, (_value, index) =>
|
|
311
317
|
formatFakeThemeBackgroundFromForeground(
|
|
312
318
|
"accent",
|
|
313
319
|
formatFakeThemeForeground(index < options.filledCells ? "warning" : "dim", "▓"),
|
|
@@ -317,23 +323,21 @@ function buildExpectedFakeContextBar(options: {
|
|
|
317
323
|
|
|
318
324
|
/**
|
|
319
325
|
* @brief Builds the expected fake pi-usereq status-bar string for assertions.
|
|
320
|
-
* @details Reconstructs the field order, separators, context bar, and
|
|
321
|
-
* @param[in] options {{
|
|
326
|
+
* @details Reconstructs the field order, separators, context bar, consolidated elapsed field, and Pushover enable field emitted by the extension using deterministic fake theme markers. Runtime is O(1). No external state is mutated.
|
|
327
|
+
* @param[in] options {{ basePath: string; contextFilledCells: number; contextPercent?: number | null; et: string; beep?: string; sound?: string; pushover?: string }} Expected status facts.
|
|
322
328
|
* @return {string} Encoded status-bar string.
|
|
323
329
|
*/
|
|
324
330
|
function buildExpectedFakeStatusText(options: {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
srcDir: string[];
|
|
330
|
-
toolCount: number;
|
|
331
|
+
basePath: string;
|
|
332
|
+
docsDir?: string;
|
|
333
|
+
testsDir?: string;
|
|
334
|
+
srcDir?: string[];
|
|
331
335
|
contextFilledCells: number;
|
|
332
336
|
contextPercent?: number | null;
|
|
333
|
-
|
|
334
|
-
last: string;
|
|
337
|
+
et: string;
|
|
335
338
|
beep?: string;
|
|
336
339
|
sound?: string;
|
|
340
|
+
pushover?: string;
|
|
337
341
|
}): string {
|
|
338
342
|
const buildField = (fieldName: string, value: string): string =>
|
|
339
343
|
`${formatFakeThemeForeground("accent", `${fieldName}:`)}${formatFakeThemeForeground("warning", value)}`;
|
|
@@ -342,20 +346,27 @@ function buildExpectedFakeStatusText(options: {
|
|
|
342
346
|
percent: options.contextPercent,
|
|
343
347
|
});
|
|
344
348
|
return [
|
|
345
|
-
buildField("
|
|
346
|
-
buildField("base", options.basePath ?? "."),
|
|
347
|
-
buildField("docs", options.docsDir),
|
|
348
|
-
buildField("tests", options.testsDir),
|
|
349
|
-
buildField("src", options.srcDir.join(",")),
|
|
350
|
-
buildField("tools", String(options.toolCount)),
|
|
349
|
+
buildField("base", options.basePath),
|
|
351
350
|
`${formatFakeThemeForeground("accent", "context:")}${contextBar}`,
|
|
352
|
-
buildField("elapsed", options.
|
|
353
|
-
buildField("
|
|
354
|
-
buildField("beep", options.beep ?? "none"),
|
|
351
|
+
buildField("elapsed", options.et),
|
|
352
|
+
buildField("beep", options.beep ?? "end,esc,err"),
|
|
355
353
|
buildField("sound", options.sound ?? "none"),
|
|
354
|
+
buildField("pushover", options.pushover ?? "off"),
|
|
356
355
|
].join(formatFakeThemeForeground("dim", " • "));
|
|
357
356
|
}
|
|
358
357
|
|
|
358
|
+
/**
|
|
359
|
+
* @brief Formats one expected absolute base path for fake status assertions.
|
|
360
|
+
* @details Resolves the supplied cwd and normalizes path separators to `/` so
|
|
361
|
+
* fake status comparisons stay stable across operating systems. Runtime is O(p)
|
|
362
|
+
* in path length. No external state is mutated.
|
|
363
|
+
* @param[in] cwd {string} Runtime working directory.
|
|
364
|
+
* @return {string} Slash-normalized absolute base path.
|
|
365
|
+
*/
|
|
366
|
+
function buildExpectedFakeBasePath(cwd: string): string {
|
|
367
|
+
return path.resolve(cwd).split(path.sep).join("/");
|
|
368
|
+
}
|
|
369
|
+
|
|
359
370
|
/**
|
|
360
371
|
* @brief Creates a fake extension command context with scripted UI interactions.
|
|
361
372
|
* @details Materializes deterministic `select`, shared settings-menu `custom`, `input`, `notify`, `setStatus`, `setEditorText`, `waitForIdle`, `newSession`, `sessionManager`, `getContextUsage`, theme-color behaviors, and captured custom-menu render output backed by in-memory state so menu-oriented and prompt-command tests can assert side effects without a real UI. Optional `onNewSession` and theme overrides let tests emulate runtime replacement and strict token validation. Runtime is O(1) plus queued interaction count and delegated new-session setup cost. Side effects are limited to in-memory state mutation.
|
|
@@ -1383,7 +1394,9 @@ test("configuration menu omits prompt-delivery controls and persisted config omi
|
|
|
1383
1394
|
});
|
|
1384
1395
|
|
|
1385
1396
|
test("configuration menus expose show-config ordering and omit overview or notification-reference rows", async () => {
|
|
1386
|
-
const cwd =
|
|
1397
|
+
const cwd = fs.mkdtempSync(
|
|
1398
|
+
path.join(process.env.HOME ?? process.cwd(), "pi-usereq-menu-structure-"),
|
|
1399
|
+
);
|
|
1387
1400
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1388
1401
|
const pi = createFakePi();
|
|
1389
1402
|
piUsereqExtension(pi);
|
|
@@ -1393,6 +1406,7 @@ test("configuration menus expose show-config ordering and omit overview or notif
|
|
|
1393
1406
|
|
|
1394
1407
|
await command!.handler("", ctx);
|
|
1395
1408
|
|
|
1409
|
+
const renderedMenu = (ctx.__state.customRenderLines[0] ?? []).join("\n");
|
|
1396
1410
|
assert.deepEqual(ctx.__state.selectCalls[0]?.items ?? [], [
|
|
1397
1411
|
"docs-dir",
|
|
1398
1412
|
"tests-dir",
|
|
@@ -1400,10 +1414,11 @@ test("configuration menus expose show-config ordering and omit overview or notif
|
|
|
1400
1414
|
"static-check",
|
|
1401
1415
|
"startup tools",
|
|
1402
1416
|
"notifications",
|
|
1403
|
-
"Reset defaults",
|
|
1404
1417
|
"show-config",
|
|
1418
|
+
"Reset defaults",
|
|
1405
1419
|
"Save and close",
|
|
1406
1420
|
]);
|
|
1421
|
+
assert.ok(renderedMenu.includes(formatRuntimePathForDisplay(getProjectConfigPath(cwd))));
|
|
1407
1422
|
assert.deepEqual(ctx.__state.selectCalls[2]?.items ?? [], [
|
|
1408
1423
|
"Toggle beep on success",
|
|
1409
1424
|
"Toggle beep on escape",
|
|
@@ -1413,6 +1428,7 @@ test("configuration menus expose show-config ordering and omit overview or notif
|
|
|
1413
1428
|
"Notify command (low vol.)",
|
|
1414
1429
|
"Notify command (mid vol.)",
|
|
1415
1430
|
"Notify command (high vol.)",
|
|
1431
|
+
"Pushover notifications",
|
|
1416
1432
|
"Back",
|
|
1417
1433
|
]);
|
|
1418
1434
|
});
|
|
@@ -1493,13 +1509,12 @@ test("session_start applies configured pi-usereq startup tools", async () => {
|
|
|
1493
1509
|
assert.equal(
|
|
1494
1510
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1495
1511
|
buildExpectedFakeStatusText({
|
|
1512
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1496
1513
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1497
1514
|
testsDir: "tests",
|
|
1498
1515
|
srcDir: ["src", "foobar"],
|
|
1499
|
-
toolCount: 2,
|
|
1500
1516
|
contextFilledCells: 0,
|
|
1501
|
-
|
|
1502
|
-
last: "N/A",
|
|
1517
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1503
1518
|
}),
|
|
1504
1519
|
);
|
|
1505
1520
|
});
|
|
@@ -1518,14 +1533,13 @@ test("session_start renders the CLEAR overlay when context usage is empty", asyn
|
|
|
1518
1533
|
assert.equal(
|
|
1519
1534
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1520
1535
|
buildExpectedFakeStatusText({
|
|
1536
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1521
1537
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1522
1538
|
testsDir: "tests",
|
|
1523
1539
|
srcDir: ["src"],
|
|
1524
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1525
1540
|
contextFilledCells: 0,
|
|
1526
1541
|
contextPercent: 0,
|
|
1527
|
-
|
|
1528
|
-
last: "N/A",
|
|
1542
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1529
1543
|
}),
|
|
1530
1544
|
);
|
|
1531
1545
|
});
|
|
@@ -1586,14 +1600,13 @@ test("context hook refreshes context usage and rounds progress cells upward", as
|
|
|
1586
1600
|
assert.equal(
|
|
1587
1601
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1588
1602
|
buildExpectedFakeStatusText({
|
|
1603
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1589
1604
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1590
1605
|
testsDir: "tests",
|
|
1591
1606
|
srcDir: ["src"],
|
|
1592
|
-
|
|
1593
|
-
contextFilledCells: 1,
|
|
1607
|
+
contextFilledCells: 2,
|
|
1594
1608
|
contextPercent: 19.1,
|
|
1595
|
-
|
|
1596
|
-
last: "N/A",
|
|
1609
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1597
1610
|
}),
|
|
1598
1611
|
);
|
|
1599
1612
|
});
|
|
@@ -1614,14 +1627,13 @@ test("context hook renders the FULL! overlay when usage exceeds ninety percent",
|
|
|
1614
1627
|
assert.equal(
|
|
1615
1628
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1616
1629
|
buildExpectedFakeStatusText({
|
|
1630
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1617
1631
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1618
1632
|
testsDir: "tests",
|
|
1619
1633
|
srcDir: ["src"],
|
|
1620
|
-
|
|
1621
|
-
contextFilledCells: 5,
|
|
1634
|
+
contextFilledCells: 10,
|
|
1622
1635
|
contextPercent: 91,
|
|
1623
|
-
|
|
1624
|
-
last: "N/A",
|
|
1636
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1625
1637
|
}),
|
|
1626
1638
|
);
|
|
1627
1639
|
});
|
|
@@ -1657,19 +1669,18 @@ test("status FULL overlay uses only CLI-supported theme tokens", async () => {
|
|
|
1657
1669
|
assert.equal(
|
|
1658
1670
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1659
1671
|
buildExpectedFakeStatusText({
|
|
1672
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1660
1673
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1661
1674
|
testsDir: "tests",
|
|
1662
1675
|
srcDir: ["src"],
|
|
1663
|
-
|
|
1664
|
-
contextFilledCells: 5,
|
|
1676
|
+
contextFilledCells: 10,
|
|
1665
1677
|
contextPercent: 91,
|
|
1666
|
-
|
|
1667
|
-
last: "N/A",
|
|
1678
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1668
1679
|
}),
|
|
1669
1680
|
);
|
|
1670
1681
|
});
|
|
1671
1682
|
|
|
1672
|
-
test("agent timing status updates
|
|
1683
|
+
test("agent timing status updates et while preserving accumulated completed runtime", async () => {
|
|
1673
1684
|
const cwd = createTempDir("pi-usereq-timing-status-");
|
|
1674
1685
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1675
1686
|
|
|
@@ -1698,13 +1709,12 @@ test("agent timing status updates elapsed and last while preserving aborted last
|
|
|
1698
1709
|
assert.equal(
|
|
1699
1710
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1700
1711
|
buildExpectedFakeStatusText({
|
|
1712
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1701
1713
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1702
1714
|
testsDir: "tests",
|
|
1703
1715
|
srcDir: ["src"],
|
|
1704
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1705
1716
|
contextFilledCells: 0,
|
|
1706
|
-
|
|
1707
|
-
last: "N/A",
|
|
1717
|
+
et: " ⏱︎ 0:00 ⚑ --:-- ⌛︎ --:--",
|
|
1708
1718
|
}),
|
|
1709
1719
|
);
|
|
1710
1720
|
|
|
@@ -1713,13 +1723,12 @@ test("agent timing status updates elapsed and last while preserving aborted last
|
|
|
1713
1723
|
assert.equal(
|
|
1714
1724
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1715
1725
|
buildExpectedFakeStatusText({
|
|
1726
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1716
1727
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1717
1728
|
testsDir: "tests",
|
|
1718
1729
|
srcDir: ["src"],
|
|
1719
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1720
1730
|
contextFilledCells: 0,
|
|
1721
|
-
|
|
1722
|
-
last: "N/A",
|
|
1731
|
+
et: " ⏱︎ 1:01 ⚑ --:-- ⌛︎ --:--",
|
|
1723
1732
|
}),
|
|
1724
1733
|
);
|
|
1725
1734
|
|
|
@@ -1735,13 +1744,12 @@ test("agent timing status updates elapsed and last while preserving aborted last
|
|
|
1735
1744
|
assert.equal(
|
|
1736
1745
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1737
1746
|
buildExpectedFakeStatusText({
|
|
1747
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1738
1748
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1739
1749
|
testsDir: "tests",
|
|
1740
1750
|
srcDir: ["src"],
|
|
1741
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1742
1751
|
contextFilledCells: 0,
|
|
1743
|
-
|
|
1744
|
-
last: "1:01",
|
|
1752
|
+
et: " ⏱︎ --:-- ⚑ 1:01 ⌛︎ 1:01",
|
|
1745
1753
|
}),
|
|
1746
1754
|
);
|
|
1747
1755
|
|
|
@@ -1760,13 +1768,12 @@ test("agent timing status updates elapsed and last while preserving aborted last
|
|
|
1760
1768
|
assert.equal(
|
|
1761
1769
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1762
1770
|
buildExpectedFakeStatusText({
|
|
1771
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1763
1772
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1764
1773
|
testsDir: "tests",
|
|
1765
1774
|
srcDir: ["src"],
|
|
1766
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1767
1775
|
contextFilledCells: 0,
|
|
1768
|
-
|
|
1769
|
-
last: "1:01",
|
|
1776
|
+
et: " ⏱︎ --:-- ⚑ 1:01 ⌛︎ 1:01",
|
|
1770
1777
|
}),
|
|
1771
1778
|
);
|
|
1772
1779
|
assert.ok(clearedHandles.length >= 2);
|
|
@@ -1794,6 +1801,14 @@ test("session_start enables default custom tools and default embedded tools only
|
|
|
1794
1801
|
assert.equal(activeTools.has("ls"), false);
|
|
1795
1802
|
});
|
|
1796
1803
|
|
|
1804
|
+
test("default configuration enables all pi-notify beep outcomes", () => {
|
|
1805
|
+
const config = getDefaultConfig(createTempDir("pi-usereq-default-notify-"));
|
|
1806
|
+
|
|
1807
|
+
assert.equal(config["notify-beep-on-end"], true);
|
|
1808
|
+
assert.equal(config["notify-beep-on-esc"], true);
|
|
1809
|
+
assert.equal(config["notify-beep-on-error"], true);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1797
1812
|
test("configuration menu can enable embedded builtin tools", async () => {
|
|
1798
1813
|
const cwd = createTempDir("pi-usereq-menu-embedded-");
|
|
1799
1814
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
@@ -1866,9 +1881,9 @@ test("configuration menu can persist pi-notify settings", async () => {
|
|
|
1866
1881
|
await command!.handler("", ctx);
|
|
1867
1882
|
|
|
1868
1883
|
const config = JSON.parse(fs.readFileSync(getProjectConfigPath(cwd), "utf8"));
|
|
1869
|
-
assert.equal(config["notify-beep-on-end"],
|
|
1870
|
-
assert.equal(config["notify-beep-on-error"],
|
|
1871
|
-
assert.equal(config["notify-beep-on-esc"],
|
|
1884
|
+
assert.equal(config["notify-beep-on-end"], false);
|
|
1885
|
+
assert.equal(config["notify-beep-on-error"], false);
|
|
1886
|
+
assert.equal(config["notify-beep-on-esc"], true);
|
|
1872
1887
|
assert.equal(config["notify-sound"], "high");
|
|
1873
1888
|
assert.equal(config["notify-sound-toggle-shortcut"], "alt+shift+s");
|
|
1874
1889
|
assert.equal(config.PI_NOTIFY_SOUND_LOW_CMD, "echo low %%INSTALLATION_PATH%%");
|
|
@@ -1877,19 +1892,285 @@ test("configuration menu can persist pi-notify settings", async () => {
|
|
|
1877
1892
|
assert.equal(
|
|
1878
1893
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1879
1894
|
buildExpectedFakeStatusText({
|
|
1895
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1880
1896
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1881
1897
|
testsDir: "tests",
|
|
1882
1898
|
srcDir: ["src"],
|
|
1883
|
-
toolCount: pi.getActiveTools().length,
|
|
1884
1899
|
contextFilledCells: 0,
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
beep: "end,err",
|
|
1900
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1901
|
+
beep: "esc",
|
|
1888
1902
|
sound: "high",
|
|
1889
1903
|
}),
|
|
1890
1904
|
);
|
|
1891
1905
|
});
|
|
1892
1906
|
|
|
1907
|
+
test("configuration menu can persist pushover settings", async () => {
|
|
1908
|
+
const cwd = createTempDir("pi-usereq-menu-pushover-");
|
|
1909
|
+
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1910
|
+
const pi = createFakePi();
|
|
1911
|
+
piUsereqExtension(pi);
|
|
1912
|
+
const command = pi.commands.get("pi-usereq");
|
|
1913
|
+
assert.ok(command);
|
|
1914
|
+
const ctx = createFakeCtx(cwd, {
|
|
1915
|
+
selects: [
|
|
1916
|
+
"notifications",
|
|
1917
|
+
"Pushover notifications",
|
|
1918
|
+
"Notify on success",
|
|
1919
|
+
"User Key/Delivery Group Key",
|
|
1920
|
+
"Token/API Token Key",
|
|
1921
|
+
"Priority",
|
|
1922
|
+
"1=High Priority",
|
|
1923
|
+
"Back",
|
|
1924
|
+
"Back",
|
|
1925
|
+
"Save and close",
|
|
1926
|
+
],
|
|
1927
|
+
inputs: [
|
|
1928
|
+
"gzfjjvp1xxmhibqwzh9m7i1zwvf83j",
|
|
1929
|
+
"ah6bf5u2sj63mcvou6qamiabeoubbe",
|
|
1930
|
+
],
|
|
1931
|
+
});
|
|
1932
|
+
|
|
1933
|
+
await command!.handler("", ctx);
|
|
1934
|
+
|
|
1935
|
+
const config = JSON.parse(fs.readFileSync(getProjectConfigPath(cwd), "utf8"));
|
|
1936
|
+
assert.equal(config["notify-pushover-global-disable"], false);
|
|
1937
|
+
assert.equal(config["notify-pushover-on-success"], true);
|
|
1938
|
+
assert.equal(config["notify-pushover-user-key"], "gzfjjvp1xxmhibqwzh9m7i1zwvf83j");
|
|
1939
|
+
assert.equal(config["notify-pushover-api-token"], "ah6bf5u2sj63mcvou6qamiabeoubbe");
|
|
1940
|
+
assert.equal(config["notify-pushover-priority"], 1);
|
|
1941
|
+
assert.equal(
|
|
1942
|
+
ctx.__state.statuses.get("pi-usereq"),
|
|
1943
|
+
buildExpectedFakeStatusText({
|
|
1944
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1945
|
+
docsDir: DEFAULT_DOCS_DIR,
|
|
1946
|
+
testsDir: "tests",
|
|
1947
|
+
srcDir: ["src"],
|
|
1948
|
+
contextFilledCells: 0,
|
|
1949
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1950
|
+
pushover: "on",
|
|
1951
|
+
}),
|
|
1952
|
+
);
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
test("successful req prompt completion sends pushover requests and suppresses invalid scenarios", async () => {
|
|
1956
|
+
const cwd = createTempDir("pi-usereq-pushover-run-");
|
|
1957
|
+
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
1958
|
+
const configPath = getProjectConfigPath(cwd);
|
|
1959
|
+
const writeConfig = (overrides: Record<string, unknown>) => {
|
|
1960
|
+
const config = {
|
|
1961
|
+
...getDefaultConfig(cwd),
|
|
1962
|
+
...overrides,
|
|
1963
|
+
};
|
|
1964
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
1965
|
+
};
|
|
1966
|
+
const pi = createFakePi();
|
|
1967
|
+
piUsereqExtension(pi);
|
|
1968
|
+
const ctx = createFakeCtx(cwd);
|
|
1969
|
+
const originalDateNow = Date.now;
|
|
1970
|
+
const recordedRequests: Array<{ url: string; options: Record<string, unknown>; body: string }> = [];
|
|
1971
|
+
let nowMs = 0;
|
|
1972
|
+
setPiNotifyHttpsRequestForTests(((url: URL, options: Record<string, unknown>, callback?: (response: any) => void) => {
|
|
1973
|
+
const record = { url: url.toString(), options, body: "" };
|
|
1974
|
+
recordedRequests.push(record);
|
|
1975
|
+
const fakeResponse = {
|
|
1976
|
+
on(_eventName: string, _handler: (...args: any[]) => void) {
|
|
1977
|
+
return fakeResponse;
|
|
1978
|
+
},
|
|
1979
|
+
resume() {
|
|
1980
|
+
return undefined;
|
|
1981
|
+
},
|
|
1982
|
+
};
|
|
1983
|
+
callback?.(fakeResponse);
|
|
1984
|
+
const fakeRequest = {
|
|
1985
|
+
on(_eventName: string, _handler: (...args: any[]) => void) {
|
|
1986
|
+
return fakeRequest;
|
|
1987
|
+
},
|
|
1988
|
+
end(body?: string) {
|
|
1989
|
+
record.body = body ?? "";
|
|
1990
|
+
},
|
|
1991
|
+
};
|
|
1992
|
+
return fakeRequest as unknown as ReturnType<typeof import("node:https").request>;
|
|
1993
|
+
}) as typeof import("node:https").request);
|
|
1994
|
+
Date.now = () => nowMs;
|
|
1995
|
+
|
|
1996
|
+
try {
|
|
1997
|
+
writeConfig({
|
|
1998
|
+
"notify-pushover-on-success": true,
|
|
1999
|
+
"notify-pushover-user-key": "gzfjjvp1xxmhibqwzh9m7i1zwvf83j",
|
|
2000
|
+
"notify-pushover-api-token": "ah6bf5u2sj63mcvou6qamiabeoubbe",
|
|
2001
|
+
"notify-pushover-priority": 1,
|
|
2002
|
+
});
|
|
2003
|
+
await pi.emit("session_start", { reason: "startup" }, ctx);
|
|
2004
|
+
recordedRequests.length = 0;
|
|
2005
|
+
nowMs = 0;
|
|
2006
|
+
await pi.commands.get("req-analyze")!.handler("Inspect src/index.ts", ctx);
|
|
2007
|
+
await pi.emit("agent_start", {}, ctx);
|
|
2008
|
+
nowMs = 5_000;
|
|
2009
|
+
await pi.emit("agent_end", {
|
|
2010
|
+
messages: [
|
|
2011
|
+
{
|
|
2012
|
+
role: "assistant",
|
|
2013
|
+
stopReason: "stop",
|
|
2014
|
+
content: [],
|
|
2015
|
+
},
|
|
2016
|
+
],
|
|
2017
|
+
}, ctx);
|
|
2018
|
+
|
|
2019
|
+
assert.equal(recordedRequests.length, 1);
|
|
2020
|
+
assert.equal(recordedRequests[0]?.url, "https://api.pushover.net/1/messages.json");
|
|
2021
|
+
const successParams = new URLSearchParams(recordedRequests[0]?.body ?? "");
|
|
2022
|
+
assert.equal(successParams.get("token"), "ah6bf5u2sj63mcvou6qamiabeoubbe");
|
|
2023
|
+
assert.equal(successParams.get("user"), "gzfjjvp1xxmhibqwzh9m7i1zwvf83j");
|
|
2024
|
+
assert.equal(successParams.get("priority"), "1");
|
|
2025
|
+
assert.equal(successParams.get("message"), "Inspect src/index.ts");
|
|
2026
|
+
assert.equal(successParams.get("title"), `analyze @ ${buildExpectedFakeBasePath(cwd)} [0:05]`);
|
|
2027
|
+
|
|
2028
|
+
recordedRequests.length = 0;
|
|
2029
|
+
nowMs = 10_000;
|
|
2030
|
+
await pi.commands.get("req-change")!.handler("Adjust docs", ctx);
|
|
2031
|
+
await pi.emit("agent_start", {}, ctx);
|
|
2032
|
+
nowMs = 14_000;
|
|
2033
|
+
await pi.emit("agent_end", {
|
|
2034
|
+
messages: [
|
|
2035
|
+
{
|
|
2036
|
+
role: "assistant",
|
|
2037
|
+
stopReason: "aborted",
|
|
2038
|
+
content: [],
|
|
2039
|
+
},
|
|
2040
|
+
],
|
|
2041
|
+
}, ctx);
|
|
2042
|
+
assert.equal(recordedRequests.length, 0);
|
|
2043
|
+
|
|
2044
|
+
recordedRequests.length = 0;
|
|
2045
|
+
nowMs = 20_000;
|
|
2046
|
+
await pi.commands.get("req-create")!.handler("Create docs", ctx);
|
|
2047
|
+
await pi.emit("agent_start", {}, ctx);
|
|
2048
|
+
nowMs = 23_000;
|
|
2049
|
+
await pi.emit("agent_end", {
|
|
2050
|
+
messages: [
|
|
2051
|
+
{
|
|
2052
|
+
role: "assistant",
|
|
2053
|
+
stopReason: "error",
|
|
2054
|
+
content: [],
|
|
2055
|
+
},
|
|
2056
|
+
],
|
|
2057
|
+
}, ctx);
|
|
2058
|
+
assert.equal(recordedRequests.length, 0);
|
|
2059
|
+
|
|
2060
|
+
writeConfig({
|
|
2061
|
+
"notify-pushover-on-success": true,
|
|
2062
|
+
"notify-pushover-user-key": "gzfjjvp1xxmhibqwzh9m7i1zwvf83j",
|
|
2063
|
+
"notify-pushover-api-token": "",
|
|
2064
|
+
"notify-pushover-priority": 1,
|
|
2065
|
+
});
|
|
2066
|
+
await pi.emit("session_start", { reason: "startup" }, ctx);
|
|
2067
|
+
recordedRequests.length = 0;
|
|
2068
|
+
nowMs = 30_000;
|
|
2069
|
+
await pi.commands.get("req-write")!.handler("Write docs", ctx);
|
|
2070
|
+
await pi.emit("agent_start", {}, ctx);
|
|
2071
|
+
nowMs = 32_000;
|
|
2072
|
+
await pi.emit("agent_end", {
|
|
2073
|
+
messages: [
|
|
2074
|
+
{
|
|
2075
|
+
role: "assistant",
|
|
2076
|
+
stopReason: "stop",
|
|
2077
|
+
content: [],
|
|
2078
|
+
},
|
|
2079
|
+
],
|
|
2080
|
+
}, ctx);
|
|
2081
|
+
assert.equal(recordedRequests.length, 0);
|
|
2082
|
+
} finally {
|
|
2083
|
+
setPiNotifyHttpsRequestForTests(undefined);
|
|
2084
|
+
Date.now = originalDateNow;
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
test("pushover global disable suppresses delivery without clearing enabled state", async () => {
|
|
2089
|
+
const cwd = createTempDir("pi-usereq-pushover-disabled-");
|
|
2090
|
+
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
2091
|
+
const pi = createFakePi();
|
|
2092
|
+
piUsereqExtension(pi);
|
|
2093
|
+
const command = pi.commands.get("pi-usereq");
|
|
2094
|
+
assert.ok(command);
|
|
2095
|
+
const ctx = createFakeCtx(cwd, {
|
|
2096
|
+
selects: [
|
|
2097
|
+
"notifications",
|
|
2098
|
+
"Pushover notifications",
|
|
2099
|
+
"Notify on success",
|
|
2100
|
+
"Global disable",
|
|
2101
|
+
"User Key/Delivery Group Key",
|
|
2102
|
+
"Token/API Token Key",
|
|
2103
|
+
"Back",
|
|
2104
|
+
"Back",
|
|
2105
|
+
"Save and close",
|
|
2106
|
+
],
|
|
2107
|
+
inputs: [
|
|
2108
|
+
"gzfjjvp1xxmhibqwzh9m7i1zwvf83j",
|
|
2109
|
+
"ah6bf5u2sj63mcvou6qamiabeoubbe",
|
|
2110
|
+
],
|
|
2111
|
+
});
|
|
2112
|
+
|
|
2113
|
+
await command!.handler("", ctx);
|
|
2114
|
+
|
|
2115
|
+
const config = JSON.parse(fs.readFileSync(getProjectConfigPath(cwd), "utf8"));
|
|
2116
|
+
assert.equal(config["notify-pushover-on-success"], true);
|
|
2117
|
+
assert.equal(config["notify-pushover-global-disable"], true);
|
|
2118
|
+
assert.equal(
|
|
2119
|
+
ctx.__state.statuses.get("pi-usereq"),
|
|
2120
|
+
buildExpectedFakeStatusText({
|
|
2121
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
2122
|
+
docsDir: DEFAULT_DOCS_DIR,
|
|
2123
|
+
testsDir: "tests",
|
|
2124
|
+
srcDir: ["src"],
|
|
2125
|
+
contextFilledCells: 0,
|
|
2126
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
2127
|
+
pushover: "on",
|
|
2128
|
+
}),
|
|
2129
|
+
);
|
|
2130
|
+
|
|
2131
|
+
const recordedRequests: string[] = [];
|
|
2132
|
+
setPiNotifyHttpsRequestForTests(((url: URL, options: Record<string, unknown>, callback?: (response: any) => void) => {
|
|
2133
|
+
void options;
|
|
2134
|
+
recordedRequests.push(url.toString());
|
|
2135
|
+
const fakeResponse = {
|
|
2136
|
+
on(_eventName: string, _handler: (...args: any[]) => void) {
|
|
2137
|
+
return fakeResponse;
|
|
2138
|
+
},
|
|
2139
|
+
resume() {
|
|
2140
|
+
return undefined;
|
|
2141
|
+
},
|
|
2142
|
+
};
|
|
2143
|
+
callback?.(fakeResponse);
|
|
2144
|
+
const fakeRequest = {
|
|
2145
|
+
on(_eventName: string, _handler: (...args: any[]) => void) {
|
|
2146
|
+
return fakeRequest;
|
|
2147
|
+
},
|
|
2148
|
+
end(_body?: string) {
|
|
2149
|
+
return undefined;
|
|
2150
|
+
},
|
|
2151
|
+
};
|
|
2152
|
+
return fakeRequest as unknown as ReturnType<typeof import("node:https").request>;
|
|
2153
|
+
}) as typeof import("node:https").request);
|
|
2154
|
+
|
|
2155
|
+
try {
|
|
2156
|
+
await pi.emit("session_start", { reason: "startup" }, ctx);
|
|
2157
|
+
await pi.commands.get("req-analyze")!.handler("Inspect src/index.ts", ctx);
|
|
2158
|
+
await pi.emit("agent_start", {}, ctx);
|
|
2159
|
+
await pi.emit("agent_end", {
|
|
2160
|
+
messages: [
|
|
2161
|
+
{
|
|
2162
|
+
role: "assistant",
|
|
2163
|
+
stopReason: "stop",
|
|
2164
|
+
content: [],
|
|
2165
|
+
},
|
|
2166
|
+
],
|
|
2167
|
+
}, ctx);
|
|
2168
|
+
assert.deepEqual(recordedRequests, []);
|
|
2169
|
+
} finally {
|
|
2170
|
+
setPiNotifyHttpsRequestForTests(undefined);
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
|
|
1893
2174
|
test("sound toggle shortcut cycles persisted pi-notify sound levels", async () => {
|
|
1894
2175
|
const cwd = createTempDir("pi-usereq-shortcut-notify-");
|
|
1895
2176
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
@@ -1914,13 +2195,12 @@ test("sound toggle shortcut cycles persisted pi-notify sound levels", async () =
|
|
|
1914
2195
|
assert.equal(
|
|
1915
2196
|
ctx.__state.statuses.get("pi-usereq"),
|
|
1916
2197
|
buildExpectedFakeStatusText({
|
|
2198
|
+
basePath: buildExpectedFakeBasePath(cwd),
|
|
1917
2199
|
docsDir: DEFAULT_DOCS_DIR,
|
|
1918
2200
|
testsDir: "tests",
|
|
1919
2201
|
srcDir: ["src"],
|
|
1920
|
-
toolCount: PI_USEREQ_DEFAULT_ENABLED_TOOL_NAMES.length,
|
|
1921
2202
|
contextFilledCells: 0,
|
|
1922
|
-
|
|
1923
|
-
last: "N/A",
|
|
2203
|
+
et: " ⏱︎ --:-- ⚑ --:-- ⌛︎ --:--",
|
|
1924
2204
|
sound: expectedSound,
|
|
1925
2205
|
}),
|
|
1926
2206
|
);
|
|
@@ -1979,6 +2259,26 @@ test("configuration menu can add static-check entries from raw specs", async ()
|
|
|
1979
2259
|
assert.deepEqual(config["static-check"].Python, [{ module: "Command", cmd: "true" }]);
|
|
1980
2260
|
});
|
|
1981
2261
|
|
|
2262
|
+
test("configuration menu hides removed static-check modules from user-facing actions", async () => {
|
|
2263
|
+
const cwd = createTempDir("pi-usereq-menu-sc-hidden-");
|
|
2264
|
+
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
2265
|
+
const pi = createFakePi();
|
|
2266
|
+
piUsereqExtension(pi);
|
|
2267
|
+
const command = pi.commands.get("pi-usereq");
|
|
2268
|
+
assert.ok(command);
|
|
2269
|
+
const ctx = createFakeCtx(cwd, {
|
|
2270
|
+
selects: ["static-check"],
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
await command!.handler("", ctx);
|
|
2274
|
+
|
|
2275
|
+
const renderedStaticCheckMenu = (ctx.__state.customRenderLines[1] ?? []).join("\n");
|
|
2276
|
+
assert.match(renderedStaticCheckMenu, /Add entry for supported language/);
|
|
2277
|
+
assert.doesNotMatch(renderedStaticCheckMenu, /Pylance/);
|
|
2278
|
+
assert.doesNotMatch(renderedStaticCheckMenu, /Ruff/);
|
|
2279
|
+
assert.doesNotMatch(renderedStaticCheckMenu, /Dummy/);
|
|
2280
|
+
});
|
|
2281
|
+
|
|
1982
2282
|
test("configuration menu can add guided static-check entries for explicit supported languages", async () => {
|
|
1983
2283
|
const cwd = createTempDir("pi-usereq-menu-sc-guided-");
|
|
1984
2284
|
fs.mkdirSync(path.dirname(getProjectConfigPath(cwd)), { recursive: true });
|
|
@@ -1994,13 +2294,13 @@ test("configuration menu can add guided static-check entries for explicit suppor
|
|
|
1994
2294
|
"static-check",
|
|
1995
2295
|
"Add entry for supported language",
|
|
1996
2296
|
"Python",
|
|
1997
|
-
"Ruff",
|
|
1998
2297
|
"Back",
|
|
1999
2298
|
"Save and close",
|
|
2000
2299
|
],
|
|
2300
|
+
inputs: ["true", ""],
|
|
2001
2301
|
}),
|
|
2002
2302
|
);
|
|
2003
2303
|
|
|
2004
2304
|
const config = JSON.parse(fs.readFileSync(getProjectConfigPath(cwd), "utf8"));
|
|
2005
|
-
assert.deepEqual(config["static-check"].Python, [{ module: "
|
|
2305
|
+
assert.deepEqual(config["static-check"].Python, [{ module: "Command", cmd: "true" }]);
|
|
2006
2306
|
});
|