gsd-pi 2.60.0-dev.d9052f5 → 2.61.0-dev.7aed0bf
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/dist/resources/extensions/ask-user-questions.js +7 -4
- package/dist/resources/extensions/gsd/auto/phases.js +15 -7
- package/dist/resources/extensions/gsd/auto-dashboard.js +21 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +6 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +58 -9
- package/dist/resources/extensions/gsd/auto-post-unit.js +3 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +36 -20
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -18
- package/dist/resources/extensions/gsd/auto-start.js +9 -5
- package/dist/resources/extensions/gsd/auto-timers.js +11 -5
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +5 -3
- package/dist/resources/extensions/gsd/auto-verification.js +3 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +120 -55
- package/dist/resources/extensions/gsd/auto.js +39 -17
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +4 -10
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +2 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -10
- package/dist/resources/extensions/gsd/commands/catalog.js +2 -0
- package/dist/resources/extensions/gsd/commands-codebase.js +48 -21
- package/dist/resources/extensions/gsd/commands-inspect.js +2 -1
- package/dist/resources/extensions/gsd/commands-maintenance.js +32 -19
- package/dist/resources/extensions/gsd/complexity-classifier.js +8 -4
- package/dist/resources/extensions/gsd/custom-verification.js +3 -2
- package/dist/resources/extensions/gsd/gsd-db.js +33 -13
- package/dist/resources/extensions/gsd/guided-flow.js +19 -9
- package/dist/resources/extensions/gsd/init-wizard.js +12 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +11 -9
- package/dist/resources/extensions/gsd/md-importer.js +5 -4
- package/dist/resources/extensions/gsd/milestone-actions.js +3 -2
- package/dist/resources/extensions/gsd/milestone-ids.js +2 -1
- package/dist/resources/extensions/gsd/model-router.js +156 -121
- package/dist/resources/extensions/gsd/parallel-merge.js +5 -3
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +26 -14
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +45 -0
- package/dist/resources/extensions/gsd/preferences.js +15 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +3 -2
- package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/dist/resources/extensions/gsd/rule-registry.js +7 -6
- package/dist/resources/extensions/gsd/safe-fs.js +6 -8
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -2
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -1
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -1
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -1
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +2 -1
- package/dist/resources/extensions/gsd/triage-resolution.js +11 -4
- package/dist/resources/extensions/gsd/workflow-events.js +2 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +37 -4
- package/dist/resources/extensions/gsd/workflow-migration.js +14 -12
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
- package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +26 -14
- package/dist/resources/extensions/shared/interview-ui.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +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/extensions/runner.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +16 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js +6 -1
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +47 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +7 -1
- package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +70 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/ask-user-questions.ts +7 -3
- package/src/resources/extensions/gsd/auto/phases.ts +17 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +22 -8
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +77 -15
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +37 -20
- package/src/resources/extensions/gsd/auto-recovery.ts +38 -18
- package/src/resources/extensions/gsd/auto-start.ts +10 -9
- package/src/resources/extensions/gsd/auto-timers.ts +12 -5
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +6 -2
- package/src/resources/extensions/gsd/auto-verification.ts +3 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +121 -55
- package/src/resources/extensions/gsd/auto.ts +40 -17
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +4 -16
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +2 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -10
- package/src/resources/extensions/gsd/commands/catalog.ts +2 -0
- package/src/resources/extensions/gsd/commands-codebase.ts +52 -20
- package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
- package/src/resources/extensions/gsd/commands-maintenance.ts +28 -19
- package/src/resources/extensions/gsd/complexity-classifier.ts +9 -4
- package/src/resources/extensions/gsd/custom-verification.ts +3 -2
- package/src/resources/extensions/gsd/gsd-db.ts +12 -14
- package/src/resources/extensions/gsd/guided-flow.ts +9 -8
- package/src/resources/extensions/gsd/init-wizard.ts +12 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +11 -17
- package/src/resources/extensions/gsd/md-importer.ts +5 -4
- package/src/resources/extensions/gsd/milestone-actions.ts +3 -2
- package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
- package/src/resources/extensions/gsd/model-router.ts +199 -173
- package/src/resources/extensions/gsd/parallel-merge.ts +5 -3
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +18 -14
- package/src/resources/extensions/gsd/preferences-types.ts +13 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +45 -0
- package/src/resources/extensions/gsd/preferences.ts +16 -3
- package/src/resources/extensions/gsd/prompt-loader.ts +3 -2
- package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
- package/src/resources/extensions/gsd/rule-registry.ts +7 -6
- package/src/resources/extensions/gsd/safe-fs.ts +6 -5
- package/src/resources/extensions/gsd/tests/capability-router.test.ts +347 -0
- package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +27 -2
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1188 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +841 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +403 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +284 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -6
- package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -6
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -6
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +3 -6
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -6
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -3
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +4 -6
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -3
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -3
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +2 -3
- package/src/resources/extensions/gsd/triage-resolution.ts +11 -4
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/workflow-events.ts +2 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +52 -5
- package/src/resources/extensions/gsd/workflow-migration.ts +14 -12
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
- package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +16 -14
- package/src/resources/extensions/shared/interview-ui.ts +3 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +144 -0
- /package/dist/web/standalone/.next/static/{JVkoVYumy0cDhOQISEYdG → b7FOoMHaUb3FPoLNbxar4}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{JVkoVYumy0cDhOQISEYdG → b7FOoMHaUb3FPoLNbxar4}/_ssgManifest.js +0 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// and encapsulates mutable hook state as instance fields.
|
|
6
6
|
//
|
|
7
7
|
// A module-level singleton accessor allows existing code to migrate incrementally.
|
|
8
|
+
import { logWarning } from "./workflow-logger.js";
|
|
8
9
|
import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js";
|
|
9
10
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
11
|
import { join } from "node:path";
|
|
@@ -310,8 +311,8 @@ export class RuleRegistry {
|
|
|
310
311
|
mkdirSync(dir, { recursive: true });
|
|
311
312
|
writeFileSync(this._hookStatePath(basePath), JSON.stringify(state, null, 2), "utf-8");
|
|
312
313
|
}
|
|
313
|
-
catch {
|
|
314
|
-
|
|
314
|
+
catch (e) {
|
|
315
|
+
logWarning("registry", `failed to persist hook state: ${e.message}`);
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
318
|
/** Restore hook cycle counts from disk after a crash/restart. */
|
|
@@ -331,8 +332,8 @@ export class RuleRegistry {
|
|
|
331
332
|
}
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
|
-
catch {
|
|
335
|
-
|
|
335
|
+
catch (e) {
|
|
336
|
+
logWarning("registry", `failed to restore hook state: ${e.message}`);
|
|
336
337
|
}
|
|
337
338
|
}
|
|
338
339
|
/** Clear persisted hook state file from disk. */
|
|
@@ -343,8 +344,8 @@ export class RuleRegistry {
|
|
|
343
344
|
writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
|
-
catch {
|
|
347
|
-
|
|
347
|
+
catch (e) {
|
|
348
|
+
logWarning("registry", `failed to clear hook state: ${e.message}`);
|
|
348
349
|
}
|
|
349
350
|
}
|
|
350
351
|
// ── Hook status reporting ───────────────────────────────────────────
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { logWarning } from "./workflow-logger.js";
|
|
3
4
|
/**
|
|
4
5
|
* Safely creates a directory. Returns true if successful, false on error.
|
|
5
|
-
* Logs
|
|
6
|
+
* Logs warnings via workflow-logger on failure.
|
|
6
7
|
*/
|
|
7
8
|
export function safeMkdir(dirPath) {
|
|
8
9
|
try {
|
|
@@ -10,14 +11,13 @@ export function safeMkdir(dirPath) {
|
|
|
10
11
|
return true;
|
|
11
12
|
}
|
|
12
13
|
catch (err) {
|
|
13
|
-
|
|
14
|
-
console.error(`[gsd] mkdir failed: ${dirPath}`, err);
|
|
14
|
+
logWarning("fs", `mkdir failed: ${dirPath}: ${err.message}`);
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Safely copies src to dst. Returns true if successful, false if src doesn't exist or copy fails.
|
|
20
|
-
* Logs
|
|
20
|
+
* Logs warnings via workflow-logger on failure.
|
|
21
21
|
*/
|
|
22
22
|
export function safeCopy(src, dst, opts) {
|
|
23
23
|
if (!existsSync(src))
|
|
@@ -27,8 +27,7 @@ export function safeCopy(src, dst, opts) {
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
catch (err) {
|
|
30
|
-
|
|
31
|
-
console.error(`[gsd] copy failed: ${src} → ${dst}`, err);
|
|
30
|
+
logWarning("fs", `copy failed: ${src} → ${dst}: ${err.message}`);
|
|
32
31
|
return false;
|
|
33
32
|
}
|
|
34
33
|
}
|
|
@@ -45,8 +44,7 @@ export function safeCopyRecursive(src, dst, opts) {
|
|
|
45
44
|
return true;
|
|
46
45
|
}
|
|
47
46
|
catch (err) {
|
|
48
|
-
|
|
49
|
-
console.error(`[gsd] recursive copy failed: ${src} → ${dst}`, err);
|
|
47
|
+
logWarning("fs", `recursive copy failed: ${src} → ${dst}: ${err.message}`);
|
|
50
48
|
return false;
|
|
51
49
|
}
|
|
52
50
|
}
|
|
@@ -15,6 +15,7 @@ import { invalidateStateCache } from "../state.js";
|
|
|
15
15
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
16
16
|
import { writeManifest } from "../workflow-manifest.js";
|
|
17
17
|
import { appendEvent } from "../workflow-events.js";
|
|
18
|
+
import { logWarning } from "../workflow-logger.js";
|
|
18
19
|
function renderMilestoneSummaryMarkdown(params) {
|
|
19
20
|
const now = new Date().toISOString();
|
|
20
21
|
const keyDecisionsYaml = params.keyDecisions.length > 0
|
|
@@ -140,7 +141,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
140
141
|
}
|
|
141
142
|
catch (renderErr) {
|
|
142
143
|
// Disk render failed — roll back DB status so state stays consistent
|
|
143
|
-
|
|
144
|
+
logWarning("tool", `complete_milestone — disk render failed, rolling back DB status: ${renderErr.message}`);
|
|
144
145
|
updateMilestoneStatus(params.milestoneId, 'active', null);
|
|
145
146
|
invalidateStateCache();
|
|
146
147
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
@@ -163,7 +164,7 @@ export async function handleCompleteMilestone(params, basePath) {
|
|
|
163
164
|
});
|
|
164
165
|
}
|
|
165
166
|
catch (hookErr) {
|
|
166
|
-
|
|
167
|
+
logWarning("tool", `complete-milestone post-mutation hook warning: ${hookErr.message}`);
|
|
167
168
|
}
|
|
168
169
|
return {
|
|
169
170
|
milestoneId: params.milestoneId,
|
|
@@ -18,6 +18,7 @@ import { renderRoadmapCheckboxes } from "../markdown-renderer.js";
|
|
|
18
18
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
19
19
|
import { writeManifest } from "../workflow-manifest.js";
|
|
20
20
|
import { appendEvent } from "../workflow-events.js";
|
|
21
|
+
import { logWarning } from "../workflow-logger.js";
|
|
21
22
|
/**
|
|
22
23
|
* Render slice summary markdown matching the template format.
|
|
23
24
|
* YAML frontmatter uses snake_case keys for parseSummary() compatibility.
|
|
@@ -240,7 +241,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
240
241
|
}
|
|
241
242
|
catch (renderErr) {
|
|
242
243
|
// Disk render failed — roll back DB status so state stays consistent
|
|
243
|
-
|
|
244
|
+
logWarning("tool", `complete_slice — disk render failed, rolling back DB status: ${renderErr.message}`);
|
|
244
245
|
updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
|
|
245
246
|
invalidateStateCache();
|
|
246
247
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
@@ -265,7 +266,7 @@ export async function handleCompleteSlice(params, basePath) {
|
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
catch (hookErr) {
|
|
268
|
-
|
|
269
|
+
logWarning("tool", `complete-slice post-mutation hook warning: ${hookErr.message}`);
|
|
269
270
|
}
|
|
270
271
|
return {
|
|
271
272
|
sliceId: params.sliceId,
|
|
@@ -18,6 +18,7 @@ import { renderPlanCheckboxes } from "../markdown-renderer.js";
|
|
|
18
18
|
import { renderAllProjections, renderSummaryContent } from "../workflow-projections.js";
|
|
19
19
|
import { writeManifest } from "../workflow-manifest.js";
|
|
20
20
|
import { appendEvent } from "../workflow-events.js";
|
|
21
|
+
import { logWarning } from "../workflow-logger.js";
|
|
21
22
|
/**
|
|
22
23
|
* Build a TaskRow-shaped object from CompleteTaskParams so the unified
|
|
23
24
|
* renderSummaryContent() can be used at completion time (#2720).
|
|
@@ -165,7 +166,7 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
165
166
|
}
|
|
166
167
|
catch (renderErr) {
|
|
167
168
|
// Disk render failed — roll back DB status so state stays consistent
|
|
168
|
-
|
|
169
|
+
logWarning("tool", `complete_task — disk render failed, rolling back DB status: ${renderErr.message}`);
|
|
169
170
|
// Delete orphaned verification_evidence rows first (FK constraint
|
|
170
171
|
// references tasks, so evidence must go before status change).
|
|
171
172
|
// Without this, retries accumulate duplicate evidence rows (#2724).
|
|
@@ -194,7 +195,7 @@ export async function handleCompleteTask(params, basePath) {
|
|
|
194
195
|
});
|
|
195
196
|
}
|
|
196
197
|
catch (hookErr) {
|
|
197
|
-
|
|
198
|
+
logWarning("tool", `complete-task post-mutation hook warning: ${hookErr.message}`);
|
|
198
199
|
}
|
|
199
200
|
return {
|
|
200
201
|
taskId: params.taskId,
|
|
@@ -7,6 +7,7 @@ import { renderRoadmapFromDb } from "../markdown-renderer.js";
|
|
|
7
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
8
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
9
9
|
import { appendEvent } from "../workflow-events.js";
|
|
10
|
+
import { logWarning } from "../workflow-logger.js";
|
|
10
11
|
function validateRiskEntries(value) {
|
|
11
12
|
if (!Array.isArray(value)) {
|
|
12
13
|
throw new Error("keyRisks must be an array");
|
|
@@ -221,7 +222,7 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
221
222
|
roadmapPath = renderResult.roadmapPath;
|
|
222
223
|
}
|
|
223
224
|
catch (renderErr) {
|
|
224
|
-
|
|
225
|
+
logWarning("tool", `plan_milestone — render failed (DB rows preserved for debugging): ${renderErr.message}`);
|
|
225
226
|
invalidateStateCache();
|
|
226
227
|
return { error: `render failed: ${renderErr.message}` };
|
|
227
228
|
}
|
|
@@ -241,7 +242,7 @@ export async function handlePlanMilestone(rawParams, basePath) {
|
|
|
241
242
|
});
|
|
242
243
|
}
|
|
243
244
|
catch (hookErr) {
|
|
244
|
-
|
|
245
|
+
logWarning("tool", `plan-milestone post-mutation hook warning: ${hookErr.message}`);
|
|
245
246
|
}
|
|
246
247
|
return {
|
|
247
248
|
milestoneId: params.milestoneId,
|
|
@@ -7,6 +7,7 @@ import { renderPlanFromDb } from "../markdown-renderer.js";
|
|
|
7
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
8
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
9
9
|
import { appendEvent } from "../workflow-events.js";
|
|
10
|
+
import { logWarning } from "../workflow-logger.js";
|
|
10
11
|
function validateTasks(value) {
|
|
11
12
|
if (!Array.isArray(value) || value.length === 0) {
|
|
12
13
|
throw new Error("tasks must be a non-empty array");
|
|
@@ -182,7 +183,7 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
182
183
|
});
|
|
183
184
|
}
|
|
184
185
|
catch (hookErr) {
|
|
185
|
-
|
|
186
|
+
logWarning("tool", `plan-slice post-mutation hook warning: ${hookErr.message}`);
|
|
186
187
|
}
|
|
187
188
|
return {
|
|
188
189
|
milestoneId: params.milestoneId,
|
|
@@ -192,7 +193,7 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
192
193
|
};
|
|
193
194
|
}
|
|
194
195
|
catch (renderErr) {
|
|
195
|
-
|
|
196
|
+
logWarning("tool", `plan_slice — render failed (DB rows preserved for debugging): ${renderErr.message}`);
|
|
196
197
|
invalidateStateCache();
|
|
197
198
|
return { error: `render failed: ${renderErr.message}` };
|
|
198
199
|
}
|
|
@@ -7,6 +7,7 @@ import { renderTaskPlanFromDb } from "../markdown-renderer.js";
|
|
|
7
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
8
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
9
9
|
import { appendEvent } from "../workflow-events.js";
|
|
10
|
+
import { logWarning } from "../workflow-logger.js";
|
|
10
11
|
function validateParams(params) {
|
|
11
12
|
if (!isNonEmptyString(params?.milestoneId))
|
|
12
13
|
throw new Error("milestoneId is required");
|
|
@@ -106,7 +107,7 @@ export async function handlePlanTask(rawParams, basePath) {
|
|
|
106
107
|
});
|
|
107
108
|
}
|
|
108
109
|
catch (hookErr) {
|
|
109
|
-
|
|
110
|
+
logWarning("tool", `plan-task post-mutation hook warning: ${hookErr.message}`);
|
|
110
111
|
}
|
|
111
112
|
return {
|
|
112
113
|
milestoneId: params.milestoneId,
|
|
@@ -9,6 +9,7 @@ import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-rendere
|
|
|
9
9
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
10
10
|
import { writeManifest } from "../workflow-manifest.js";
|
|
11
11
|
import { appendEvent } from "../workflow-events.js";
|
|
12
|
+
import { logWarning } from "../workflow-logger.js";
|
|
12
13
|
function validateParams(params) {
|
|
13
14
|
if (!isNonEmptyString(params?.milestoneId))
|
|
14
15
|
throw new Error("milestoneId is required");
|
|
@@ -182,9 +183,8 @@ export async function handleReassessRoadmap(rawParams, basePath) {
|
|
|
182
183
|
if (existsSync(validationFile))
|
|
183
184
|
unlinkSync(validationFile);
|
|
184
185
|
}
|
|
185
|
-
catch {
|
|
186
|
-
|
|
187
|
-
// will not see the file-based verdict as authoritative.
|
|
186
|
+
catch (e) {
|
|
187
|
+
logWarning("tool", `validation file cleanup failed: ${e.message}`);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
// ── Invalidate caches ─────────────────────────────────────────
|
|
@@ -204,7 +204,7 @@ export async function handleReassessRoadmap(rawParams, basePath) {
|
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
catch (hookErr) {
|
|
207
|
-
|
|
207
|
+
logWarning("tool", `reassess-roadmap post-mutation hook warning: ${hookErr.message}`);
|
|
208
208
|
}
|
|
209
209
|
return {
|
|
210
210
|
milestoneId: params.milestoneId,
|
|
@@ -15,6 +15,7 @@ import { isClosedStatus } from "../status-guards.js";
|
|
|
15
15
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
16
16
|
import { writeManifest } from "../workflow-manifest.js";
|
|
17
17
|
import { appendEvent } from "../workflow-events.js";
|
|
18
|
+
import { logWarning } from "../workflow-logger.js";
|
|
18
19
|
export async function handleReopenSlice(params, basePath) {
|
|
19
20
|
// ── Validate required fields ────────────────────────────────────────────
|
|
20
21
|
if (!params.sliceId || typeof params.sliceId !== "string" || params.sliceId.trim() === "") {
|
|
@@ -77,7 +78,7 @@ export async function handleReopenSlice(params, basePath) {
|
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
80
|
catch (hookErr) {
|
|
80
|
-
|
|
81
|
+
logWarning("tool", `reopen-slice post-mutation hook warning: ${hookErr.message}`);
|
|
81
82
|
}
|
|
82
83
|
return {
|
|
83
84
|
milestoneId: params.milestoneId,
|
|
@@ -14,6 +14,7 @@ import { isClosedStatus } from "../status-guards.js";
|
|
|
14
14
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
15
15
|
import { writeManifest } from "../workflow-manifest.js";
|
|
16
16
|
import { appendEvent } from "../workflow-events.js";
|
|
17
|
+
import { logWarning } from "../workflow-logger.js";
|
|
17
18
|
export async function handleReopenTask(params, basePath) {
|
|
18
19
|
// ── Validate required fields ────────────────────────────────────────────
|
|
19
20
|
if (!params.taskId || typeof params.taskId !== "string" || params.taskId.trim() === "") {
|
|
@@ -81,7 +82,7 @@ export async function handleReopenTask(params, basePath) {
|
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
catch (hookErr) {
|
|
84
|
-
|
|
85
|
+
logWarning("tool", `reopen-task post-mutation hook warning: ${hookErr.message}`);
|
|
85
86
|
}
|
|
86
87
|
return {
|
|
87
88
|
milestoneId: params.milestoneId,
|
|
@@ -7,6 +7,7 @@ import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
|
|
|
7
7
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
8
8
|
import { writeManifest } from "../workflow-manifest.js";
|
|
9
9
|
import { appendEvent } from "../workflow-events.js";
|
|
10
|
+
import { logWarning } from "../workflow-logger.js";
|
|
10
11
|
function validateParams(params) {
|
|
11
12
|
if (!isNonEmptyString(params?.milestoneId))
|
|
12
13
|
throw new Error("milestoneId is required");
|
|
@@ -173,7 +174,7 @@ export async function handleReplanSlice(rawParams, basePath) {
|
|
|
173
174
|
});
|
|
174
175
|
}
|
|
175
176
|
catch (hookErr) {
|
|
176
|
-
|
|
177
|
+
logWarning("tool", `replan-slice post-mutation hook warning: ${hookErr.message}`);
|
|
177
178
|
}
|
|
178
179
|
return {
|
|
179
180
|
milestoneId: params.milestoneId,
|
|
@@ -15,6 +15,7 @@ import { saveFile, clearParseCache } from "../files.js";
|
|
|
15
15
|
import { invalidateStateCache } from "../state.js";
|
|
16
16
|
import { VALIDATION_VERDICTS, isValidMilestoneVerdict } from "../verdict-parser.js";
|
|
17
17
|
import { insertMilestoneValidationGates } from "../milestone-validation-gates.js";
|
|
18
|
+
import { logWarning } from "../workflow-logger.js";
|
|
18
19
|
function renderValidationMarkdown(params) {
|
|
19
20
|
let md = `---
|
|
20
21
|
verdict: ${params.verdict}
|
|
@@ -95,7 +96,7 @@ export async function handleValidateMilestone(params, basePath) {
|
|
|
95
96
|
await saveFile(validationPath, validationMd);
|
|
96
97
|
}
|
|
97
98
|
catch (renderErr) {
|
|
98
|
-
|
|
99
|
+
logWarning("tool", `validate_milestone — disk render failed, rolling back DB row: ${renderErr.message}`);
|
|
99
100
|
deleteAssessmentByScope(params.milestoneId, 'milestone-validation');
|
|
100
101
|
return { error: `disk render failed: ${renderErr.message}` };
|
|
101
102
|
}
|
|
@@ -113,10 +113,17 @@ export function executeReplan(basePath, mid, sid, capture) {
|
|
|
113
113
|
*/
|
|
114
114
|
export function executeBacktrack(basePath, currentMilestoneId, capture) {
|
|
115
115
|
try {
|
|
116
|
-
// Extract target milestone from capture text or resolution
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
116
|
+
// Extract target milestone from capture text or resolution.
|
|
117
|
+
// Filter out the current milestone ID to avoid picking it as the backtrack target
|
|
118
|
+
// when the text mentions both current and target milestones (e.g. "backtrack from M004 to M003").
|
|
119
|
+
const sourceText = capture.resolution ?? capture.text;
|
|
120
|
+
const allMatches = [...sourceText.matchAll(/\b(M\d{3}(?:-[a-z0-9]{6})?)\b/g)]
|
|
121
|
+
.map(m => m[1])
|
|
122
|
+
.filter(id => id !== currentMilestoneId);
|
|
123
|
+
// Reject ambiguous multi-target strings — if more than one distinct target remains,
|
|
124
|
+
// don't guess; let the user clarify.
|
|
125
|
+
const uniqueTargets = [...new Set(allMatches)];
|
|
126
|
+
const targetMilestoneId = uniqueTargets.length === 1 ? uniqueTargets[0] : null;
|
|
120
127
|
const ts = new Date().toISOString();
|
|
121
128
|
const triggerPath = join(gsdRoot(basePath), "BACKTRACK-TRIGGER.md");
|
|
122
129
|
const content = [
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { appendFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
5
|
+
import { logWarning } from "./workflow-logger.js";
|
|
5
6
|
// ─── Session ID ───────────────────────────────────────────────────────────
|
|
6
7
|
/**
|
|
7
8
|
* Engine-generated session ID — stable for the lifetime of this process.
|
|
@@ -49,7 +50,7 @@ export function readEvents(logPath) {
|
|
|
49
50
|
events.push(JSON.parse(line));
|
|
50
51
|
}
|
|
51
52
|
catch {
|
|
52
|
-
|
|
53
|
+
logWarning("event-log", `skipping corrupted event line (${line.length} bytes)`);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
return events;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Centralized warning/error accumulator for the workflow engine pipeline.
|
|
3
3
|
// Captures structured entries that the auto-loop can drain after each unit
|
|
4
4
|
// to surface root causes for stuck loops, silent degradation, and blocked writes.
|
|
5
|
-
//
|
|
5
|
+
// Error-severity entries are persisted to .gsd/audit-log.jsonl (sanitized) for
|
|
6
|
+
// post-mortem analysis. Warnings are ephemeral (stderr + buffer only) to avoid
|
|
7
|
+
// log amplification from expected-control-flow catch paths.
|
|
6
8
|
//
|
|
7
9
|
// Stderr policy: every logWarning/logError call writes immediately to stderr
|
|
8
10
|
// for terminal visibility. This is intentional — unlike debug-logger (which is
|
|
@@ -177,12 +179,15 @@ function _push(severity, component, message, context) {
|
|
|
177
179
|
if (_buffer.length > MAX_BUFFER) {
|
|
178
180
|
_buffer.shift();
|
|
179
181
|
}
|
|
180
|
-
// Persist to .gsd/audit-log.jsonl so
|
|
181
|
-
|
|
182
|
+
// Persist errors to .gsd/audit-log.jsonl so they survive context resets.
|
|
183
|
+
// Only error-severity entries are persisted — warnings are ephemeral (stderr + buffer)
|
|
184
|
+
// to avoid log amplification from expected-control-flow catch paths.
|
|
185
|
+
if (_auditBasePath && severity === "error") {
|
|
182
186
|
try {
|
|
183
187
|
const auditDir = join(_auditBasePath, ".gsd");
|
|
184
188
|
mkdirSync(auditDir, { recursive: true });
|
|
185
|
-
|
|
189
|
+
const sanitized = _sanitizeForAudit(entry);
|
|
190
|
+
appendFileSync(join(auditDir, "audit-log.jsonl"), JSON.stringify(sanitized) + "\n", "utf-8");
|
|
186
191
|
}
|
|
187
192
|
catch (auditErr) {
|
|
188
193
|
// Best-effort — never let audit write failures bubble up
|
|
@@ -190,3 +195,31 @@ function _push(severity, component, message, context) {
|
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Sanitize a log entry before persisting to the audit JSONL file.
|
|
200
|
+
* Strips potentially sensitive context (raw paths, cwd, full error text)
|
|
201
|
+
* to avoid leaking local environment details into durable telemetry.
|
|
202
|
+
*/
|
|
203
|
+
function _sanitizeForAudit(entry) {
|
|
204
|
+
const sanitized = {
|
|
205
|
+
ts: entry.ts,
|
|
206
|
+
severity: entry.severity,
|
|
207
|
+
component: entry.component,
|
|
208
|
+
// Truncate message to avoid persisting oversized raw error dumps
|
|
209
|
+
message: entry.message.length > 200 ? entry.message.slice(0, 200) + "…[truncated]" : entry.message,
|
|
210
|
+
};
|
|
211
|
+
if (entry.context) {
|
|
212
|
+
// Allowlist: only persist known-safe structured keys
|
|
213
|
+
const SAFE_KEYS = new Set(["fn", "tool", "mid", "sid", "tid", "worktree"]);
|
|
214
|
+
const filtered = {};
|
|
215
|
+
for (const [k, v] of Object.entries(entry.context)) {
|
|
216
|
+
if (SAFE_KEYS.has(k)) {
|
|
217
|
+
filtered[k] = v;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (Object.keys(filtered).length > 0) {
|
|
221
|
+
sanitized.context = filtered;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return sanitized;
|
|
225
|
+
}
|
|
@@ -6,6 +6,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { _getAdapter, transaction } from "./gsd-db.js";
|
|
8
8
|
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
9
|
+
import { logWarning } from "./workflow-logger.js";
|
|
9
10
|
// ─── needsAutoMigration ───────────────────────────────────────────────────
|
|
10
11
|
/**
|
|
11
12
|
* Returns true when engine tables are empty AND a .gsd/milestones/ directory
|
|
@@ -22,8 +23,8 @@ export function needsAutoMigration(basePath) {
|
|
|
22
23
|
if (row && row["cnt"] > 0)
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
|
-
catch {
|
|
26
|
-
|
|
26
|
+
catch (e) {
|
|
27
|
+
logWarning("migration", `DB probe failed: ${e.message}`);
|
|
27
28
|
return false;
|
|
28
29
|
}
|
|
29
30
|
// Check if .gsd/milestones/ directory exists
|
|
@@ -66,7 +67,7 @@ export function migrateFromMarkdown(basePath) {
|
|
|
66
67
|
.map(e => e.name);
|
|
67
68
|
}
|
|
68
69
|
catch {
|
|
69
|
-
|
|
70
|
+
logWarning("migration", "failed to read milestones directory");
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
if (milestoneDirs.length === 0) {
|
|
@@ -102,7 +103,7 @@ export function migrateFromMarkdown(basePath) {
|
|
|
102
103
|
}));
|
|
103
104
|
}
|
|
104
105
|
catch (err) {
|
|
105
|
-
|
|
106
|
+
logWarning("migration", `failed to parse ROADMAP.md for ${mId}: ${err.message}`);
|
|
106
107
|
// Still add milestone with ID as title
|
|
107
108
|
milestoneInserts.push({ id: mId, title: mId, status: milestoneStatus });
|
|
108
109
|
}
|
|
@@ -148,7 +149,7 @@ export function migrateFromMarkdown(basePath) {
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
catch (err) {
|
|
151
|
-
|
|
152
|
+
logWarning("migration", `failed to parse ${slice.id}-PLAN.md for ${mId}: ${err.message}`);
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
}
|
|
@@ -163,8 +164,8 @@ export function migrateFromMarkdown(basePath) {
|
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
|
-
catch {
|
|
167
|
-
|
|
167
|
+
catch (e) {
|
|
168
|
+
logWarning("migration", `Orphaned summary check failed for ${mId}: ${e.message}`);
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
// Execute all inserts atomically
|
|
@@ -245,19 +246,20 @@ export function validateMigration(basePath) {
|
|
|
245
246
|
const plan = parsePlan(planContent);
|
|
246
247
|
mdTaskCount += plan.tasks.length;
|
|
247
248
|
}
|
|
248
|
-
catch {
|
|
249
|
-
|
|
249
|
+
catch (e) {
|
|
250
|
+
logWarning("migration", `Failed to read plan ${slice.id}-PLAN.md: ${e.message}`);
|
|
250
251
|
}
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
}
|
|
254
|
-
catch {
|
|
255
|
-
|
|
255
|
+
catch (e) {
|
|
256
|
+
logWarning("migration", `Failed to read roadmap for ${mId}: ${e.message}`);
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
}
|
|
260
|
-
catch {
|
|
261
|
+
catch (e) {
|
|
262
|
+
logWarning("migration", `Validation failed to read markdown: ${e.message}`);
|
|
261
263
|
return { discrepancies: ["Failed to read markdown for validation"] };
|
|
262
264
|
}
|
|
263
265
|
// Compare counts
|
|
@@ -371,7 +371,7 @@ export function regenerateIfMissing(basePath, milestoneId, sliceId, fileType) {
|
|
|
371
371
|
regenerated++;
|
|
372
372
|
}
|
|
373
373
|
catch (err) {
|
|
374
|
-
|
|
374
|
+
logWarning("projection", `regenerateIfMissing SUMMARY failed for ${task.id}: ${err.message}`);
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
}
|
|
@@ -399,7 +399,7 @@ export function regenerateIfMissing(basePath, milestoneId, sliceId, fileType) {
|
|
|
399
399
|
return true;
|
|
400
400
|
}
|
|
401
401
|
catch (err) {
|
|
402
|
-
|
|
402
|
+
logWarning("projection", `regenerateIfMissing ${fileType} failed: ${err.message}`);
|
|
403
403
|
return false;
|
|
404
404
|
}
|
|
405
405
|
}
|
|
@@ -375,8 +375,8 @@ function parseEventBlock(block) {
|
|
|
375
375
|
try {
|
|
376
376
|
params = JSON.parse(paramsMatch[1]);
|
|
377
377
|
}
|
|
378
|
-
catch {
|
|
379
|
-
|
|
378
|
+
catch (e) {
|
|
379
|
+
logWarning("reconcile", `tool call params parse failed: ${e.message}`);
|
|
380
380
|
}
|
|
381
381
|
i++; // consume params line
|
|
382
382
|
}
|