gsd-pi 2.18.0 → 2.20.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/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +690 -39
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +654 -36
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +84 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/dist/resources/extensions/gsd/preferences.ts +198 -150
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/dist/resources/extensions/remote-questions/format.ts +166 -14
- package/dist/resources/extensions/remote-questions/manager.ts +14 -4
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +148 -39
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +690 -39
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +654 -36
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +51 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +84 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +72 -2
- package/src/resources/extensions/gsd/preferences.ts +198 -150
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +488 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +290 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +478 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +505 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +337 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +755 -0
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +35 -4
- package/src/resources/extensions/remote-questions/format.ts +166 -14
- package/src/resources/extensions/remote-questions/manager.ts +14 -4
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -1,87 +1,240 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Routing History — structural tests for adaptive learning module.
|
|
3
|
-
*
|
|
4
|
-
* Verifies routing-history.ts exports and structure from #579.
|
|
5
|
-
* Uses source-level checks to avoid @gsd/pi-coding-agent import chain.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import test from "node:test";
|
|
9
2
|
import assert from "node:assert/strict";
|
|
10
|
-
import { readFileSync } from "node:fs";
|
|
11
|
-
import { join
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
import { mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
initRoutingHistory,
|
|
9
|
+
resetRoutingHistory,
|
|
10
|
+
recordOutcome,
|
|
11
|
+
recordFeedback,
|
|
12
|
+
getAdaptiveTierAdjustment,
|
|
13
|
+
clearRoutingHistory,
|
|
14
|
+
getRoutingHistory,
|
|
15
|
+
} from "../routing-history.js";
|
|
16
|
+
|
|
17
|
+
// ─── Test Setup ──────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function makeTmpDir(): string {
|
|
20
|
+
const dir = join(tmpdir(), `gsd-routing-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
21
|
+
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cleanup(dir: string): void {
|
|
26
|
+
try { rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
27
|
+
resetRoutingHistory();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── recordOutcome ───────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
test("recordOutcome tracks success and failure counts", () => {
|
|
33
|
+
const dir = makeTmpDir();
|
|
34
|
+
try {
|
|
35
|
+
initRoutingHistory(dir);
|
|
36
|
+
recordOutcome("execute-task", "standard", true);
|
|
37
|
+
recordOutcome("execute-task", "standard", true);
|
|
38
|
+
recordOutcome("execute-task", "standard", false);
|
|
39
|
+
|
|
40
|
+
const history = getRoutingHistory();
|
|
41
|
+
assert.ok(history);
|
|
42
|
+
const pattern = history.patterns["execute-task"];
|
|
43
|
+
assert.ok(pattern);
|
|
44
|
+
assert.equal(pattern.standard.success, 2);
|
|
45
|
+
assert.equal(pattern.standard.fail, 1);
|
|
46
|
+
} finally {
|
|
47
|
+
cleanup(dir);
|
|
48
|
+
}
|
|
23
49
|
});
|
|
24
50
|
|
|
25
|
-
test("
|
|
26
|
-
|
|
51
|
+
test("recordOutcome tracks tag-specific patterns", () => {
|
|
52
|
+
const dir = makeTmpDir();
|
|
53
|
+
try {
|
|
54
|
+
initRoutingHistory(dir);
|
|
55
|
+
recordOutcome("execute-task", "light", true, ["docs"]);
|
|
56
|
+
|
|
57
|
+
const history = getRoutingHistory();
|
|
58
|
+
assert.ok(history);
|
|
59
|
+
assert.ok(history.patterns["execute-task:docs"]);
|
|
60
|
+
assert.equal(history.patterns["execute-task:docs"].light.success, 1);
|
|
61
|
+
} finally {
|
|
62
|
+
cleanup(dir);
|
|
63
|
+
}
|
|
27
64
|
});
|
|
28
65
|
|
|
29
|
-
test("
|
|
30
|
-
|
|
66
|
+
test("recordOutcome applies rolling window", () => {
|
|
67
|
+
const dir = makeTmpDir();
|
|
68
|
+
try {
|
|
69
|
+
initRoutingHistory(dir);
|
|
70
|
+
// Record 60 successes — should be capped to 50
|
|
71
|
+
for (let i = 0; i < 60; i++) {
|
|
72
|
+
recordOutcome("execute-task", "standard", true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const history = getRoutingHistory();
|
|
76
|
+
assert.ok(history);
|
|
77
|
+
const total = history.patterns["execute-task"].standard.success +
|
|
78
|
+
history.patterns["execute-task"].standard.fail;
|
|
79
|
+
assert.ok(total <= 50, `total ${total} should be <= 50`);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanup(dir);
|
|
82
|
+
}
|
|
31
83
|
});
|
|
32
84
|
|
|
33
|
-
|
|
34
|
-
|
|
85
|
+
// ─── getAdaptiveTierAdjustment ───────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
test("no adjustment when insufficient data", () => {
|
|
88
|
+
const dir = makeTmpDir();
|
|
89
|
+
try {
|
|
90
|
+
initRoutingHistory(dir);
|
|
91
|
+
recordOutcome("execute-task", "light", false);
|
|
92
|
+
// Only 1 data point — not enough
|
|
93
|
+
const adj = getAdaptiveTierAdjustment("execute-task", "light");
|
|
94
|
+
assert.equal(adj, null);
|
|
95
|
+
} finally {
|
|
96
|
+
cleanup(dir);
|
|
97
|
+
}
|
|
35
98
|
});
|
|
36
99
|
|
|
37
|
-
test("
|
|
38
|
-
|
|
100
|
+
test("bumps tier when failure rate exceeds threshold", () => {
|
|
101
|
+
const dir = makeTmpDir();
|
|
102
|
+
try {
|
|
103
|
+
initRoutingHistory(dir);
|
|
104
|
+
// Record high failure rate at light tier
|
|
105
|
+
recordOutcome("execute-task", "light", false);
|
|
106
|
+
recordOutcome("execute-task", "light", false);
|
|
107
|
+
recordOutcome("execute-task", "light", true);
|
|
108
|
+
// 2/3 = 66% failure rate > 20% threshold
|
|
109
|
+
|
|
110
|
+
const adj = getAdaptiveTierAdjustment("execute-task", "light");
|
|
111
|
+
assert.equal(adj, "standard");
|
|
112
|
+
} finally {
|
|
113
|
+
cleanup(dir);
|
|
114
|
+
}
|
|
39
115
|
});
|
|
40
116
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
117
|
+
test("no adjustment when success rate is high", () => {
|
|
118
|
+
const dir = makeTmpDir();
|
|
119
|
+
try {
|
|
120
|
+
initRoutingHistory(dir);
|
|
121
|
+
for (let i = 0; i < 10; i++) {
|
|
122
|
+
recordOutcome("execute-task", "light", true);
|
|
123
|
+
}
|
|
124
|
+
const adj = getAdaptiveTierAdjustment("execute-task", "light");
|
|
125
|
+
assert.equal(adj, null);
|
|
126
|
+
} finally {
|
|
127
|
+
cleanup(dir);
|
|
128
|
+
}
|
|
47
129
|
});
|
|
48
130
|
|
|
49
|
-
test("
|
|
50
|
-
|
|
131
|
+
test("tag-specific patterns take precedence", () => {
|
|
132
|
+
const dir = makeTmpDir();
|
|
133
|
+
try {
|
|
134
|
+
initRoutingHistory(dir);
|
|
135
|
+
// Base pattern has high success rate (tagged calls also count toward base)
|
|
136
|
+
for (let i = 0; i < 15; i++) {
|
|
137
|
+
recordOutcome("execute-task", "light", true);
|
|
138
|
+
}
|
|
139
|
+
// But docs-tagged tasks fail at light
|
|
140
|
+
recordOutcome("execute-task", "light", false, ["docs"]);
|
|
141
|
+
recordOutcome("execute-task", "light", false, ["docs"]);
|
|
142
|
+
recordOutcome("execute-task", "light", true, ["docs"]);
|
|
143
|
+
|
|
144
|
+
// With tags, should bump (docs pattern: 1/3 success = 66% failure)
|
|
145
|
+
const adj = getAdaptiveTierAdjustment("execute-task", "light", ["docs"]);
|
|
146
|
+
assert.equal(adj, "standard");
|
|
147
|
+
|
|
148
|
+
// Without tags, should not bump (base: 16/18 success = 11% failure)
|
|
149
|
+
const adjBase = getAdaptiveTierAdjustment("execute-task", "light");
|
|
150
|
+
assert.equal(adjBase, null);
|
|
151
|
+
} finally {
|
|
152
|
+
cleanup(dir);
|
|
153
|
+
}
|
|
51
154
|
});
|
|
52
155
|
|
|
53
|
-
|
|
54
|
-
|
|
156
|
+
// ─── recordFeedback ──────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
test("recordFeedback stores feedback entries", () => {
|
|
159
|
+
const dir = makeTmpDir();
|
|
160
|
+
try {
|
|
161
|
+
initRoutingHistory(dir);
|
|
162
|
+
recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
|
|
163
|
+
|
|
164
|
+
const history = getRoutingHistory();
|
|
165
|
+
assert.ok(history);
|
|
166
|
+
assert.equal(history.feedback.length, 1);
|
|
167
|
+
assert.equal(history.feedback[0].rating, "over");
|
|
168
|
+
assert.equal(history.feedback[0].tier, "standard");
|
|
169
|
+
} finally {
|
|
170
|
+
cleanup(dir);
|
|
171
|
+
}
|
|
55
172
|
});
|
|
56
173
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
174
|
+
test("recordFeedback 'under' increases failure count at tier", () => {
|
|
175
|
+
const dir = makeTmpDir();
|
|
176
|
+
try {
|
|
177
|
+
initRoutingHistory(dir);
|
|
178
|
+
recordFeedback("execute-task", "M001/S01/T01", "light", "under");
|
|
179
|
+
|
|
180
|
+
const history = getRoutingHistory();
|
|
181
|
+
assert.ok(history);
|
|
182
|
+
// "under" adds 2 (FEEDBACK_WEIGHT) failures
|
|
183
|
+
assert.equal(history.patterns["execute-task"].light.fail, 2);
|
|
184
|
+
} finally {
|
|
185
|
+
cleanup(dir);
|
|
186
|
+
}
|
|
70
187
|
});
|
|
71
188
|
|
|
72
|
-
test("
|
|
73
|
-
|
|
189
|
+
test("recordFeedback 'over' increases success count at lower tier", () => {
|
|
190
|
+
const dir = makeTmpDir();
|
|
191
|
+
try {
|
|
192
|
+
initRoutingHistory(dir);
|
|
193
|
+
recordFeedback("execute-task", "M001/S01/T01", "standard", "over");
|
|
194
|
+
|
|
195
|
+
const history = getRoutingHistory();
|
|
196
|
+
assert.ok(history);
|
|
197
|
+
// "over" at standard → adds 2 successes at light
|
|
198
|
+
assert.equal(history.patterns["execute-task"].light.success, 2);
|
|
199
|
+
} finally {
|
|
200
|
+
cleanup(dir);
|
|
201
|
+
}
|
|
74
202
|
});
|
|
75
203
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
204
|
+
// ─── clearRoutingHistory ─────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
test("clearRoutingHistory resets all data", () => {
|
|
207
|
+
const dir = makeTmpDir();
|
|
208
|
+
try {
|
|
209
|
+
initRoutingHistory(dir);
|
|
210
|
+
recordOutcome("execute-task", "light", true);
|
|
211
|
+
clearRoutingHistory(dir);
|
|
212
|
+
|
|
213
|
+
const history = getRoutingHistory();
|
|
214
|
+
assert.ok(history);
|
|
215
|
+
assert.deepEqual(history.patterns, {});
|
|
216
|
+
assert.deepEqual(history.feedback, []);
|
|
217
|
+
} finally {
|
|
218
|
+
cleanup(dir);
|
|
219
|
+
}
|
|
82
220
|
});
|
|
83
221
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
222
|
+
// ─── Persistence ─────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
test("routing history persists to disk and reloads", () => {
|
|
225
|
+
const dir = makeTmpDir();
|
|
226
|
+
try {
|
|
227
|
+
initRoutingHistory(dir);
|
|
228
|
+
recordOutcome("execute-task", "standard", true);
|
|
229
|
+
recordOutcome("execute-task", "standard", true);
|
|
230
|
+
resetRoutingHistory();
|
|
231
|
+
|
|
232
|
+
// Reload from disk
|
|
233
|
+
initRoutingHistory(dir);
|
|
234
|
+
const history = getRoutingHistory();
|
|
235
|
+
assert.ok(history);
|
|
236
|
+
assert.equal(history.patterns["execute-task"].standard.success, 2);
|
|
237
|
+
} finally {
|
|
238
|
+
cleanup(dir);
|
|
239
|
+
}
|
|
87
240
|
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for skill telemetry and skill health (#599).
|
|
3
|
+
* Tests the pure functions — no file I/O, no extension context.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, beforeEach } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import type { UnitMetrics } from "../metrics.js";
|
|
9
|
+
|
|
10
|
+
// ─── Test helpers ─────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
function makeUnit(overrides: Partial<UnitMetrics> = {}): UnitMetrics {
|
|
13
|
+
return {
|
|
14
|
+
type: "execute-task",
|
|
15
|
+
id: "M001/S01/T01",
|
|
16
|
+
model: "claude-sonnet-4-20250514",
|
|
17
|
+
startedAt: 1000,
|
|
18
|
+
finishedAt: 2000,
|
|
19
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
20
|
+
cost: 0.05,
|
|
21
|
+
toolCalls: 3,
|
|
22
|
+
assistantMessages: 5,
|
|
23
|
+
userMessages: 2,
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Skill Telemetry ──────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
describe("skill-telemetry", () => {
|
|
31
|
+
// Note: captureAvailableSkills/getAndClearSkills depend on filesystem (getAgentDir)
|
|
32
|
+
// so we test the data flow via getSkillLastUsed and detectStaleSkills which are pure
|
|
33
|
+
|
|
34
|
+
it("getSkillLastUsed returns most recent timestamp per skill", async () => {
|
|
35
|
+
const { getSkillLastUsed } = await import("../skill-telemetry.js");
|
|
36
|
+
|
|
37
|
+
const units = [
|
|
38
|
+
makeUnit({ finishedAt: 1000, skills: ["rust-core", "axum-web-framework"] }),
|
|
39
|
+
makeUnit({ finishedAt: 2000, skills: ["rust-core"] }),
|
|
40
|
+
makeUnit({ finishedAt: 3000, skills: ["axum-web-framework"] }),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const result = getSkillLastUsed(units);
|
|
44
|
+
assert.equal(result.get("rust-core"), 2000);
|
|
45
|
+
assert.equal(result.get("axum-web-framework"), 3000);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("getSkillLastUsed returns empty map for units without skills", async () => {
|
|
49
|
+
const { getSkillLastUsed } = await import("../skill-telemetry.js");
|
|
50
|
+
|
|
51
|
+
const units = [makeUnit(), makeUnit()];
|
|
52
|
+
const result = getSkillLastUsed(units);
|
|
53
|
+
assert.equal(result.size, 0);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ─── Skill Health ─────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
describe("skill-health", () => {
|
|
60
|
+
it("buildHealSkillPrompt includes unit ID", async () => {
|
|
61
|
+
const { buildHealSkillPrompt } = await import("../skill-health.js");
|
|
62
|
+
const prompt = buildHealSkillPrompt("M001/S01/T01");
|
|
63
|
+
assert.ok(prompt.includes("M001/S01/T01"));
|
|
64
|
+
assert.ok(prompt.includes("Skill Heal Analysis"));
|
|
65
|
+
assert.ok(prompt.includes("skill-review-queue.md"));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("computeStaleAvoidList excludes already-avoided skills", async () => {
|
|
69
|
+
// This test requires filesystem access for loadLedgerFromDisk
|
|
70
|
+
// so we test the filtering logic conceptually
|
|
71
|
+
const { computeStaleAvoidList } = await import("../skill-health.js");
|
|
72
|
+
|
|
73
|
+
// With no metrics file, should return empty
|
|
74
|
+
const result = computeStaleAvoidList("/nonexistent/path", ["some-skill"]);
|
|
75
|
+
assert.ok(Array.isArray(result));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ─── UnitMetrics skills field ─────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("UnitMetrics skills field", () => {
|
|
82
|
+
it("skills field is optional and accepts string array", () => {
|
|
83
|
+
const unit = makeUnit({ skills: ["rust-core", "axum-web-framework"] });
|
|
84
|
+
assert.deepEqual(unit.skills, ["rust-core", "axum-web-framework"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("skills field is undefined when not provided", () => {
|
|
88
|
+
const unit = makeUnit();
|
|
89
|
+
assert.equal(unit.skills, undefined);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ─── Preferences ──────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe("skill_staleness_days preference", () => {
|
|
96
|
+
it("validates valid staleness days", async () => {
|
|
97
|
+
const { validatePreferences } = await import("../preferences.js");
|
|
98
|
+
|
|
99
|
+
const result = validatePreferences({ skill_staleness_days: 30 });
|
|
100
|
+
assert.equal(result.preferences.skill_staleness_days, 30);
|
|
101
|
+
assert.equal(result.errors.length, 0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("validates zero (disabled) staleness days", async () => {
|
|
105
|
+
const { validatePreferences } = await import("../preferences.js");
|
|
106
|
+
|
|
107
|
+
const result = validatePreferences({ skill_staleness_days: 0 });
|
|
108
|
+
assert.equal(result.preferences.skill_staleness_days, 0);
|
|
109
|
+
assert.equal(result.errors.length, 0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("rejects negative staleness days", async () => {
|
|
113
|
+
const { validatePreferences } = await import("../preferences.js");
|
|
114
|
+
|
|
115
|
+
const result = validatePreferences({ skill_staleness_days: -5 });
|
|
116
|
+
assert.equal(result.preferences.skill_staleness_days, undefined);
|
|
117
|
+
assert.ok(result.errors.some(e => e.includes("skill_staleness_days")));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("floors fractional days", async () => {
|
|
121
|
+
const { validatePreferences } = await import("../preferences.js");
|
|
122
|
+
|
|
123
|
+
const result = validatePreferences({ skill_staleness_days: 30.7 });
|
|
124
|
+
assert.equal(result.preferences.skill_staleness_days, 30);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -4,7 +4,7 @@ import { mkdirSync, rmSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
-
import {
|
|
7
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
8
8
|
|
|
9
9
|
import { writeFileSync } from "node:fs";
|
|
10
10
|
import {
|
|
@@ -25,6 +25,27 @@ function cleanup(base: string): void {
|
|
|
25
25
|
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function waitForChildExit(child: ChildProcess, timeoutMs = 5000): Promise<number | null> {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
if (child.exitCode !== null) {
|
|
31
|
+
resolve(child.exitCode);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const timeout = setTimeout(() => {
|
|
36
|
+
child.off("exit", onExit);
|
|
37
|
+
resolve(child.exitCode);
|
|
38
|
+
}, timeoutMs);
|
|
39
|
+
|
|
40
|
+
const onExit = (code: number | null) => {
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
resolve(code);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
child.once("exit", onExit);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
// ─── stopAutoRemote ──────────────────────────────────────────────────────
|
|
29
50
|
|
|
30
51
|
test("stopAutoRemote returns found:false when no lock file exists", () => {
|
|
@@ -63,12 +84,16 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", as
|
|
|
63
84
|
const base = makeTmpBase();
|
|
64
85
|
|
|
65
86
|
// Spawn a child process that sleeps, acting as a fake auto-mode session
|
|
66
|
-
const child =
|
|
67
|
-
|
|
68
|
-
["process.on('SIGTERM', () => process.exit(0)); setTimeout(() => process.exit(1), 30000);"],
|
|
87
|
+
const child = spawn(
|
|
88
|
+
process.execPath,
|
|
89
|
+
["-e", "process.on('SIGTERM', () => process.exit(0)); setTimeout(() => process.exit(1), 30000);"],
|
|
69
90
|
{ stdio: "ignore", detached: false },
|
|
70
91
|
);
|
|
71
92
|
|
|
93
|
+
if (!child.pid) {
|
|
94
|
+
throw new Error("failed to spawn child process for stopAutoRemote test");
|
|
95
|
+
}
|
|
96
|
+
|
|
72
97
|
try {
|
|
73
98
|
// Wait for child to be ready
|
|
74
99
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
@@ -84,15 +109,13 @@ test("stopAutoRemote sends SIGTERM to a live process and returns found:true", as
|
|
|
84
109
|
};
|
|
85
110
|
writeFileSync(join(base, ".gsd", "auto.lock"), JSON.stringify(lockData, null, 2), "utf-8");
|
|
86
111
|
|
|
112
|
+
const exitPromise = waitForChildExit(child);
|
|
87
113
|
const result = stopAutoRemote(base);
|
|
88
114
|
assert.equal(result.found, true, "should find running auto-mode");
|
|
89
115
|
assert.equal(result.pid, child.pid, "should return the PID");
|
|
90
116
|
|
|
91
117
|
// Wait for child to exit (it should receive SIGTERM)
|
|
92
|
-
const exitCode = await
|
|
93
|
-
child.on("exit", (code) => resolve(code));
|
|
94
|
-
setTimeout(() => resolve(null), 5000);
|
|
95
|
-
});
|
|
118
|
+
const exitCode = await exitPromise;
|
|
96
119
|
// On Windows, SIGTERM is not interceptable — the process exits with code 1
|
|
97
120
|
// rather than running the handler. Accept either clean exit (0) or forced (1).
|
|
98
121
|
assert.ok(exitCode !== null, "child should have exited after SIGTERM");
|