gsd-pi 2.19.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-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- 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 +41 -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 +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- 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/system.md +2 -1
- 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/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/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/model-isolation.test.ts +99 -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 +262 -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/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/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 +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- 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/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 +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- 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 +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- 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-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- 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 +41 -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 +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- 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/system.md +2 -1
- 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/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/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/model-isolation.test.ts +99 -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 +262 -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/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/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 +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- 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/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 +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- 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 +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- 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
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
renderDepsView,
|
|
7
7
|
renderMetricsView,
|
|
8
8
|
renderTimelineView,
|
|
9
|
+
renderAgentView,
|
|
10
|
+
renderChangelogView,
|
|
11
|
+
renderExportView,
|
|
9
12
|
} from "../visualizer-views.js";
|
|
10
13
|
import type { VisualizerData } from "../visualizer-data.js";
|
|
11
14
|
import { createTestContext } from "./test-helpers.ts";
|
|
@@ -30,6 +33,15 @@ function makeVisualizerData(overrides: Partial<VisualizerData> = {}): Visualizer
|
|
|
30
33
|
bySlice: [],
|
|
31
34
|
byModel: [],
|
|
32
35
|
units: [],
|
|
36
|
+
criticalPath: {
|
|
37
|
+
milestonePath: [],
|
|
38
|
+
slicePath: [],
|
|
39
|
+
milestoneSlack: new Map(),
|
|
40
|
+
sliceSlack: new Map(),
|
|
41
|
+
},
|
|
42
|
+
remainingSliceCount: 0,
|
|
43
|
+
agentActivity: null,
|
|
44
|
+
changelog: { entries: [] },
|
|
33
45
|
...overrides,
|
|
34
46
|
};
|
|
35
47
|
}
|
|
@@ -104,6 +116,73 @@ console.log("\n=== renderProgressView ===");
|
|
|
104
116
|
assertEq(lines.length, 0, "empty milestones produce no lines");
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
// ─── Risk Heatmap ───────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
console.log("\n=== Risk Heatmap ===");
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
const data = makeVisualizerData({
|
|
125
|
+
milestones: [
|
|
126
|
+
{
|
|
127
|
+
id: "M001",
|
|
128
|
+
title: "First",
|
|
129
|
+
status: "active",
|
|
130
|
+
dependsOn: [],
|
|
131
|
+
slices: [
|
|
132
|
+
{ id: "S01", title: "A", done: true, active: false, risk: "low", depends: [], tasks: [] },
|
|
133
|
+
{ id: "S02", title: "B", done: false, active: true, risk: "high", depends: [], tasks: [] },
|
|
134
|
+
{ id: "S03", title: "C", done: false, active: false, risk: "medium", depends: [], tasks: [] },
|
|
135
|
+
{ id: "S04", title: "D", done: false, active: false, risk: "high", depends: [], tasks: [] },
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const lines = renderProgressView(data, mockTheme, 80);
|
|
142
|
+
assertTrue(lines.some(l => l.includes("Risk Heatmap")), "heatmap header present");
|
|
143
|
+
assertTrue(lines.some(l => l.includes("██")), "heatmap has colored blocks");
|
|
144
|
+
assertTrue(lines.some(l => l.includes("low") && l.includes("med") && l.includes("high")), "heatmap legend present");
|
|
145
|
+
assertTrue(lines.some(l => l.includes("1 low, 1 med, 2 high")), "risk summary counts");
|
|
146
|
+
assertTrue(lines.some(l => l.includes("1 high-risk not started")), "high-risk not started warning");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Search/Filter ──────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
console.log("\n=== Search/Filter ===");
|
|
152
|
+
|
|
153
|
+
{
|
|
154
|
+
const data = makeVisualizerData({
|
|
155
|
+
milestones: [
|
|
156
|
+
{
|
|
157
|
+
id: "M001",
|
|
158
|
+
title: "Auth",
|
|
159
|
+
status: "active",
|
|
160
|
+
dependsOn: [],
|
|
161
|
+
slices: [
|
|
162
|
+
{ id: "S01", title: "JWT", done: false, active: false, risk: "low", depends: [], tasks: [] },
|
|
163
|
+
{ id: "S02", title: "OAuth", done: false, active: false, risk: "high", depends: [], tasks: [] },
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "M002",
|
|
168
|
+
title: "Dashboard",
|
|
169
|
+
status: "pending",
|
|
170
|
+
dependsOn: ["M001"],
|
|
171
|
+
slices: [],
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Filter by keyword "auth"
|
|
177
|
+
const filtered = renderProgressView(data, mockTheme, 80, { text: "auth", field: "all" });
|
|
178
|
+
assertTrue(filtered.some(l => l.includes("M001")), "filter shows matching milestone");
|
|
179
|
+
assertTrue(filtered.some(l => l.includes("Filter (all): auth")), "filter indicator present");
|
|
180
|
+
|
|
181
|
+
// Filter by risk "high"
|
|
182
|
+
const riskFiltered = renderProgressView(data, mockTheme, 80, { text: "high", field: "risk" });
|
|
183
|
+
assertTrue(riskFiltered.some(l => l.includes("M001")), "risk filter shows milestone with high-risk slice");
|
|
184
|
+
}
|
|
185
|
+
|
|
107
186
|
// ─── renderDepsView ─────────────────────────────────────────────────────────
|
|
108
187
|
|
|
109
188
|
console.log("\n=== renderDepsView ===");
|
|
@@ -129,12 +208,20 @@ console.log("\n=== renderDepsView ===");
|
|
|
129
208
|
slices: [],
|
|
130
209
|
},
|
|
131
210
|
],
|
|
211
|
+
criticalPath: {
|
|
212
|
+
milestonePath: ["M001", "M002"],
|
|
213
|
+
slicePath: ["S01", "S02"],
|
|
214
|
+
milestoneSlack: new Map([["M001", 0], ["M002", 0]]),
|
|
215
|
+
sliceSlack: new Map([["S01", 0], ["S02", 0]]),
|
|
216
|
+
},
|
|
132
217
|
});
|
|
133
218
|
|
|
134
219
|
const lines = renderDepsView(data, mockTheme, 80);
|
|
135
220
|
assertTrue(lines.length > 0, "deps view produces output");
|
|
136
221
|
assertTrue(lines.some(l => l.includes("M001") && l.includes("M002")), "shows milestone dep edge");
|
|
137
222
|
assertTrue(lines.some(l => l.includes("S01") && l.includes("S02")), "shows slice dep edge");
|
|
223
|
+
assertTrue(lines.some(l => l.includes("Critical Path")), "shows critical path section");
|
|
224
|
+
assertTrue(lines.some(l => l.includes("[CRITICAL]")), "shows CRITICAL badge");
|
|
138
225
|
}
|
|
139
226
|
|
|
140
227
|
{
|
|
@@ -162,6 +249,8 @@ console.log("\n=== renderMetricsView ===");
|
|
|
162
249
|
toolCalls: 15,
|
|
163
250
|
assistantMessages: 10,
|
|
164
251
|
userMessages: 5,
|
|
252
|
+
totalTruncationSections: 0,
|
|
253
|
+
continueHereFiredCount: 0,
|
|
165
254
|
},
|
|
166
255
|
byPhase: [
|
|
167
256
|
{
|
|
@@ -187,6 +276,11 @@ console.log("\n=== renderMetricsView ===");
|
|
|
187
276
|
cost: 2.50,
|
|
188
277
|
},
|
|
189
278
|
],
|
|
279
|
+
bySlice: [
|
|
280
|
+
{ sliceId: "M001/S01", units: 3, tokens: { input: 600, output: 300, cacheRead: 100, cacheWrite: 50, total: 1050 }, cost: 1.50, duration: 40000 },
|
|
281
|
+
{ sliceId: "M001/S02", units: 2, tokens: { input: 400, output: 200, cacheRead: 100, cacheWrite: 50, total: 750 }, cost: 1.00, duration: 20000 },
|
|
282
|
+
],
|
|
283
|
+
remainingSliceCount: 3,
|
|
190
284
|
});
|
|
191
285
|
|
|
192
286
|
const lines = renderMetricsView(data, mockTheme, 80);
|
|
@@ -194,6 +288,11 @@ console.log("\n=== renderMetricsView ===");
|
|
|
194
288
|
assertTrue(lines.some(l => l.includes("$2.50")), "shows total cost");
|
|
195
289
|
assertTrue(lines.some(l => l.includes("execution")), "shows phase name");
|
|
196
290
|
assertTrue(lines.some(l => l.includes("claude-opus-4-6")), "shows model name");
|
|
291
|
+
assertTrue(lines.some(l => l.includes("Projections")), "shows projections section");
|
|
292
|
+
assertTrue(lines.some(l => l.includes("Avg cost/slice")), "shows avg cost per slice");
|
|
293
|
+
assertTrue(lines.some(l => l.includes("Projected remaining")), "shows projected remaining");
|
|
294
|
+
assertTrue(lines.some(l => l.includes("Burn rate")), "shows burn rate");
|
|
295
|
+
assertTrue(lines.some(l => l.includes("Cost trend")), "shows sparkline");
|
|
197
296
|
}
|
|
198
297
|
|
|
199
298
|
{
|
|
@@ -237,11 +336,16 @@ console.log("\n=== renderTimelineView ===");
|
|
|
237
336
|
],
|
|
238
337
|
});
|
|
239
338
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
assertTrue(
|
|
243
|
-
|
|
244
|
-
|
|
339
|
+
// Wide terminal — Gantt view
|
|
340
|
+
const ganttLines = renderTimelineView(data, mockTheme, 120);
|
|
341
|
+
assertTrue(ganttLines.length >= 2, "gantt view produces lines for each unit");
|
|
342
|
+
|
|
343
|
+
// Narrow terminal — list view
|
|
344
|
+
const listLines = renderTimelineView(data, mockTheme, 80);
|
|
345
|
+
assertTrue(listLines.length >= 2, "list view produces lines for each unit");
|
|
346
|
+
assertTrue(listLines.some(l => l.includes("execute-task")), "shows unit type");
|
|
347
|
+
assertTrue(listLines.some(l => l.includes("M001/S01/T01")), "shows unit id");
|
|
348
|
+
assertTrue(listLines.some(l => l.includes("$0.42")), "shows unit cost");
|
|
245
349
|
}
|
|
246
350
|
|
|
247
351
|
{
|
|
@@ -250,6 +354,125 @@ console.log("\n=== renderTimelineView ===");
|
|
|
250
354
|
assertTrue(lines.some(l => l.includes("No execution history")), "shows empty message");
|
|
251
355
|
}
|
|
252
356
|
|
|
357
|
+
// ─── renderAgentView ────────────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
console.log("\n=== renderAgentView ===");
|
|
360
|
+
|
|
361
|
+
{
|
|
362
|
+
const now = Date.now();
|
|
363
|
+
const data = makeVisualizerData({
|
|
364
|
+
agentActivity: {
|
|
365
|
+
currentUnit: { type: "execute-task", id: "M001/S02/T03", startedAt: now - 60000 },
|
|
366
|
+
elapsed: 60000,
|
|
367
|
+
completedUnits: 8,
|
|
368
|
+
totalSlices: 15,
|
|
369
|
+
completionRate: 2.4,
|
|
370
|
+
active: true,
|
|
371
|
+
sessionCost: 1.23,
|
|
372
|
+
sessionTokens: 45200,
|
|
373
|
+
},
|
|
374
|
+
units: [
|
|
375
|
+
{
|
|
376
|
+
type: "execute-task", id: "M001/S01/T01", model: "claude-opus-4-6",
|
|
377
|
+
startedAt: now - 300000, finishedAt: now - 240000,
|
|
378
|
+
tokens: { input: 500, output: 200, cacheRead: 100, cacheWrite: 50, total: 850 },
|
|
379
|
+
cost: 0.12, toolCalls: 5, assistantMessages: 3, userMessages: 1,
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const lines = renderAgentView(data, mockTheme, 80);
|
|
385
|
+
assertTrue(lines.length > 0, "agent view produces output");
|
|
386
|
+
assertTrue(lines.some(l => l.includes("ACTIVE")), "shows active status");
|
|
387
|
+
assertTrue(lines.some(l => l.includes("M001/S02/T03")), "shows current unit");
|
|
388
|
+
assertTrue(lines.some(l => l.includes("8/15")), "shows progress fraction");
|
|
389
|
+
assertTrue(lines.some(l => l.includes("2.4 units/hr")), "shows completion rate");
|
|
390
|
+
assertTrue(lines.some(l => l.includes("$1.23")), "shows session cost");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
{
|
|
394
|
+
const data = makeVisualizerData({ agentActivity: null });
|
|
395
|
+
const lines = renderAgentView(data, mockTheme, 80);
|
|
396
|
+
assertTrue(lines.some(l => l.includes("No agent activity")), "shows no-activity message");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
const data = makeVisualizerData({
|
|
401
|
+
agentActivity: {
|
|
402
|
+
currentUnit: null,
|
|
403
|
+
elapsed: 0,
|
|
404
|
+
completedUnits: 5,
|
|
405
|
+
totalSlices: 10,
|
|
406
|
+
completionRate: 1.5,
|
|
407
|
+
active: false,
|
|
408
|
+
sessionCost: 0.50,
|
|
409
|
+
sessionTokens: 20000,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const lines = renderAgentView(data, mockTheme, 80);
|
|
414
|
+
assertTrue(lines.some(l => l.includes("IDLE")), "shows idle status");
|
|
415
|
+
assertTrue(lines.some(l => l.includes("Not in auto mode")), "shows not-in-auto message");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ─── renderChangelogView ────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
console.log("\n=== renderChangelogView ===");
|
|
421
|
+
|
|
422
|
+
{
|
|
423
|
+
const data = makeVisualizerData({
|
|
424
|
+
changelog: {
|
|
425
|
+
entries: [
|
|
426
|
+
{
|
|
427
|
+
milestoneId: "M001",
|
|
428
|
+
sliceId: "S01",
|
|
429
|
+
title: "Core Authentication Setup",
|
|
430
|
+
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
431
|
+
filesModified: [
|
|
432
|
+
{ path: "src/auth/jwt.ts", description: "JWT token generation and validation" },
|
|
433
|
+
{ path: "src/auth/middleware.ts", description: "Express middleware for auth checks" },
|
|
434
|
+
],
|
|
435
|
+
completedAt: "2026-03-15T14:30:00Z",
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const lines = renderChangelogView(data, mockTheme, 80);
|
|
442
|
+
assertTrue(lines.length > 0, "changelog view produces output");
|
|
443
|
+
assertTrue(lines.some(l => l.includes("M001/S01")), "shows slice reference");
|
|
444
|
+
assertTrue(lines.some(l => l.includes("Core Authentication Setup")), "shows entry title");
|
|
445
|
+
assertTrue(lines.some(l => l.includes("JWT-based auth")), "shows one-liner");
|
|
446
|
+
assertTrue(lines.some(l => l.includes("src/auth/jwt.ts")), "shows modified file");
|
|
447
|
+
assertTrue(lines.some(l => l.includes("2026-03-15")), "shows completed date");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
{
|
|
451
|
+
const data = makeVisualizerData({ changelog: { entries: [] } });
|
|
452
|
+
const lines = renderChangelogView(data, mockTheme, 80);
|
|
453
|
+
assertTrue(lines.some(l => l.includes("No completed slices")), "shows empty state");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ─── renderExportView ───────────────────────────────────────────────────────
|
|
457
|
+
|
|
458
|
+
console.log("\n=== renderExportView ===");
|
|
459
|
+
|
|
460
|
+
{
|
|
461
|
+
const data = makeVisualizerData();
|
|
462
|
+
const lines = renderExportView(data, mockTheme, 80);
|
|
463
|
+
assertTrue(lines.some(l => l.includes("Export Options")), "shows export header");
|
|
464
|
+
assertTrue(lines.some(l => l.includes("[m]")), "shows markdown option");
|
|
465
|
+
assertTrue(lines.some(l => l.includes("[j]")), "shows json option");
|
|
466
|
+
assertTrue(lines.some(l => l.includes("[s]")), "shows snapshot option");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
{
|
|
470
|
+
const data = makeVisualizerData();
|
|
471
|
+
const lines = renderExportView(data, mockTheme, 80, "/tmp/export-2026.md");
|
|
472
|
+
assertTrue(lines.some(l => l.includes("Last export:")), "shows last export path");
|
|
473
|
+
assertTrue(lines.some(l => l.includes("/tmp/export-2026.md")), "shows specific export path");
|
|
474
|
+
}
|
|
475
|
+
|
|
253
476
|
// ─── Report ─────────────────────────────────────────────────────────────────
|
|
254
477
|
|
|
255
478
|
report();
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-db-integration.test.ts
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for the worktree DB copy and reconcile hooks.
|
|
5
|
+
* Uses real temp git repos and real SQLite databases.
|
|
6
|
+
*
|
|
7
|
+
* Test cases:
|
|
8
|
+
* 1. Copy: createAutoWorktree seeds .gsd/gsd.db into the worktree when main has one
|
|
9
|
+
* 2. Copy-skip: createAutoWorktree silently skips when main has no gsd.db
|
|
10
|
+
* 3. Reconcile: reconcileWorktreeDb merges worktree rows into main DB
|
|
11
|
+
* 4. Reconcile-skip: reconcileWorktreeDb is non-fatal when both paths are nonexistent
|
|
12
|
+
* 5. Failure path: reconcileWorktreeDb emits to stderr on open failure (observable)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, realpathSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { execSync } from "node:child_process";
|
|
19
|
+
|
|
20
|
+
import { createAutoWorktree } from "../auto-worktree.ts";
|
|
21
|
+
import { worktreePath } from "../worktree-manager.ts";
|
|
22
|
+
import {
|
|
23
|
+
copyWorktreeDb,
|
|
24
|
+
reconcileWorktreeDb,
|
|
25
|
+
openDatabase,
|
|
26
|
+
closeDatabase,
|
|
27
|
+
upsertDecision,
|
|
28
|
+
getActiveDecisions,
|
|
29
|
+
isDbAvailable,
|
|
30
|
+
} from "../gsd-db.ts";
|
|
31
|
+
|
|
32
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
33
|
+
|
|
34
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
35
|
+
|
|
36
|
+
function run(command: string, cwd: string): string {
|
|
37
|
+
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createTempRepo(): string {
|
|
41
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-db-int-test-")));
|
|
42
|
+
run("git init", dir);
|
|
43
|
+
run("git config user.email test@test.com", dir);
|
|
44
|
+
run("git config user.name Test", dir);
|
|
45
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
46
|
+
run("git add .", dir);
|
|
47
|
+
run("git commit -m init", dir);
|
|
48
|
+
run("git branch -M main", dir);
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main(): Promise<void> {
|
|
53
|
+
const savedCwd = process.cwd();
|
|
54
|
+
const tempDirs: string[] = [];
|
|
55
|
+
|
|
56
|
+
function makeTempDir(): string {
|
|
57
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-db-int-")));
|
|
58
|
+
tempDirs.push(dir);
|
|
59
|
+
return dir;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
|
|
64
|
+
// ─── Test 1: copy on worktree creation ───────────────────────────
|
|
65
|
+
console.log("\n=== Test 1: copy on worktree creation ===");
|
|
66
|
+
{
|
|
67
|
+
const tempDir = createTempRepo();
|
|
68
|
+
tempDirs.push(tempDir);
|
|
69
|
+
|
|
70
|
+
// Seed a gsd.db in the main repo
|
|
71
|
+
const gsdDir = join(tempDir, ".gsd");
|
|
72
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
73
|
+
const mainDbPath = join(gsdDir, "gsd.db");
|
|
74
|
+
openDatabase(mainDbPath);
|
|
75
|
+
closeDatabase();
|
|
76
|
+
|
|
77
|
+
// Commit so createAutoWorktree can copy planning artifacts
|
|
78
|
+
run("git add .", tempDir);
|
|
79
|
+
run('git commit -m "add gsd dir"', tempDir);
|
|
80
|
+
|
|
81
|
+
// createAutoWorktree should copy the DB into the worktree
|
|
82
|
+
const wtPath = createAutoWorktree(tempDir, "M004");
|
|
83
|
+
|
|
84
|
+
const worktreeDbPath = join(worktreePath(tempDir, "M004"), ".gsd", "gsd.db");
|
|
85
|
+
assertTrue(
|
|
86
|
+
existsSync(worktreeDbPath),
|
|
87
|
+
"gsd.db exists in worktree .gsd after createAutoWorktree",
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Restore cwd for next test
|
|
91
|
+
process.chdir(savedCwd);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Test 2: copy skip when no source DB ─────────────────────────
|
|
95
|
+
console.log("\n=== Test 2: copy skip when no source DB ===");
|
|
96
|
+
{
|
|
97
|
+
const tempDir = createTempRepo();
|
|
98
|
+
tempDirs.push(tempDir);
|
|
99
|
+
|
|
100
|
+
// No gsd.db — just a bare repo
|
|
101
|
+
let threw = false;
|
|
102
|
+
let wtPath: string | null = null;
|
|
103
|
+
try {
|
|
104
|
+
wtPath = createAutoWorktree(tempDir, "M004");
|
|
105
|
+
} catch (err) {
|
|
106
|
+
threw = true;
|
|
107
|
+
console.error(" Unexpected throw:", err);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
assertTrue(!threw, "createAutoWorktree does not throw when no source DB");
|
|
111
|
+
|
|
112
|
+
const worktreeDbPath = join(worktreePath(tempDir, "M004"), ".gsd", "gsd.db");
|
|
113
|
+
assertTrue(
|
|
114
|
+
!existsSync(worktreeDbPath),
|
|
115
|
+
"gsd.db is absent in worktree when source had none",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
process.chdir(savedCwd);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ─── Test 3: reconcile inserts worktree rows into main ───────────
|
|
122
|
+
console.log("\n=== Test 3: reconcile merges worktree rows into main ===");
|
|
123
|
+
{
|
|
124
|
+
const mainDbPath = join(makeTempDir(), "main.db");
|
|
125
|
+
const worktreeDbPath = join(makeTempDir(), "wt.db");
|
|
126
|
+
|
|
127
|
+
// Seed main DB (empty schema)
|
|
128
|
+
openDatabase(mainDbPath);
|
|
129
|
+
closeDatabase();
|
|
130
|
+
|
|
131
|
+
// Seed worktree DB with one decision
|
|
132
|
+
openDatabase(worktreeDbPath);
|
|
133
|
+
upsertDecision({
|
|
134
|
+
id: "D-WT-001",
|
|
135
|
+
when_context: "integration test",
|
|
136
|
+
scope: "test",
|
|
137
|
+
decision: "use reconcile",
|
|
138
|
+
choice: "reconcile on merge",
|
|
139
|
+
rationale: "test coverage",
|
|
140
|
+
revisable: "no",
|
|
141
|
+
superseded_by: null,
|
|
142
|
+
});
|
|
143
|
+
closeDatabase();
|
|
144
|
+
|
|
145
|
+
// Reconcile worktree → main
|
|
146
|
+
const result = reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
147
|
+
assertTrue(result.decisions >= 1, "reconcile reports at least 1 decision merged");
|
|
148
|
+
|
|
149
|
+
// Open main DB and verify the row is present
|
|
150
|
+
openDatabase(mainDbPath);
|
|
151
|
+
const decisions = getActiveDecisions();
|
|
152
|
+
closeDatabase();
|
|
153
|
+
|
|
154
|
+
const found = decisions.some((d) => d.id === "D-WT-001");
|
|
155
|
+
assertTrue(found, "worktree decision D-WT-001 present in main DB after reconcile");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── Test 4: reconcile non-fatal when both paths nonexistent ─────
|
|
159
|
+
console.log("\n=== Test 4: reconcile non-fatal on nonexistent paths ===");
|
|
160
|
+
{
|
|
161
|
+
let threw = false;
|
|
162
|
+
try {
|
|
163
|
+
reconcileWorktreeDb("/nonexistent/path/gsd.db", "/also/nonexistent/gsd.db");
|
|
164
|
+
} catch {
|
|
165
|
+
threw = true;
|
|
166
|
+
}
|
|
167
|
+
assertTrue(!threw, "reconcileWorktreeDb does not throw when worktree DB is absent");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Test 5: failure path observable via stderr (diagnostic) ─────
|
|
171
|
+
// reconcileWorktreeDb emits to stderr on reconciliation failures.
|
|
172
|
+
// We can't easily intercept stderr in this test harness, but we verify
|
|
173
|
+
// that the function returns the zero-result shape (not undefined/throws)
|
|
174
|
+
// when the worktree DB is missing — confirming the failure path is non-fatal
|
|
175
|
+
// and returns a structured result.
|
|
176
|
+
console.log("\n=== Test 5: reconcile returns zero-shape when worktree DB absent ===");
|
|
177
|
+
{
|
|
178
|
+
const mainDbPath = join(makeTempDir(), "main2.db");
|
|
179
|
+
openDatabase(mainDbPath);
|
|
180
|
+
closeDatabase();
|
|
181
|
+
|
|
182
|
+
const result = reconcileWorktreeDb(mainDbPath, "/definitely/does/not/exist.db");
|
|
183
|
+
assertEq(result.decisions, 0, "decisions is 0 when worktree DB absent");
|
|
184
|
+
assertEq(result.requirements, 0, "requirements is 0 when worktree DB absent");
|
|
185
|
+
assertEq(result.artifacts, 0, "artifacts is 0 when worktree DB absent");
|
|
186
|
+
assertEq(result.conflicts.length, 0, "conflicts is empty when worktree DB absent");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
} finally {
|
|
190
|
+
// Always restore cwd
|
|
191
|
+
process.chdir(savedCwd);
|
|
192
|
+
// Ensure DB is closed
|
|
193
|
+
if (isDbAvailable()) closeDatabase();
|
|
194
|
+
// Remove all temp dirs
|
|
195
|
+
for (const dir of tempDirs) {
|
|
196
|
+
if (existsSync(dir)) {
|
|
197
|
+
rmSync(dir, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
report();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main();
|