@opengsd/gsd-pi 1.1.1-dev.154fd443 → 1.1.1-dev.1854a79a

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.
Files changed (169) hide show
  1. package/dist/project-sessions.js +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/index.js +39 -22
  4. package/dist/resources/extensions/browser-tools/state.js +12 -0
  5. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  6. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
  8. package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
  9. package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
  10. package/dist/resources/extensions/gsd/auto-prompts.js +14 -1
  11. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  12. package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
  13. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
  14. package/dist/resources/extensions/gsd/auto.js +62 -464
  15. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -1
  16. package/dist/resources/extensions/gsd/debug-logger.js +10 -0
  17. package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
  18. package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
  19. package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
  20. package/dist/resources/extensions/gsd/native-git-bridge.js +9 -0
  21. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  22. package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
  23. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
  24. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
  25. package/dist/web/standalone/.next/BUILD_ID +1 -1
  26. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  27. package/dist/web/standalone/.next/build-manifest.json +2 -2
  28. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  29. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  49. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  50. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  51. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  52. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  53. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  54. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  55. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  57. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  58. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  59. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  60. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  61. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  62. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  63. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  64. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  65. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  66. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  67. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  69. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  71. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  73. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  75. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  77. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  79. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/index.html +1 -1
  81. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  88. package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
  89. package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
  90. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  91. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  93. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  94. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  95. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  96. package/package.json +6 -4
  97. package/packages/cloud-mcp-gateway/package.json +2 -2
  98. package/packages/contracts/package.json +1 -1
  99. package/packages/daemon/package.json +4 -4
  100. package/packages/gsd-agent-core/package.json +5 -5
  101. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  102. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  103. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  104. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
  105. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  106. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  108. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  110. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  111. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  112. package/packages/gsd-agent-modes/package.json +7 -7
  113. package/packages/mcp-server/package.json +3 -3
  114. package/packages/native/package.json +1 -1
  115. package/packages/pi-agent-core/package.json +1 -1
  116. package/packages/pi-ai/dist/models.generated.d.ts +0 -34
  117. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  118. package/packages/pi-ai/dist/models.generated.js +0 -34
  119. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  120. package/packages/pi-ai/package.json +1 -1
  121. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
  123. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  125. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  126. package/packages/pi-coding-agent/package.json +7 -7
  127. package/packages/pi-tui/package.json +2 -2
  128. package/packages/rpc-client/package.json +2 -2
  129. package/pkg/package.json +1 -1
  130. package/src/resources/extensions/browser-tools/index.ts +39 -22
  131. package/src/resources/extensions/browser-tools/state.ts +13 -0
  132. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  133. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  134. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  135. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
  136. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
  137. package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
  138. package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
  139. package/src/resources/extensions/gsd/auto-prompts.ts +21 -1
  140. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  141. package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
  142. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
  143. package/src/resources/extensions/gsd/auto.ts +81 -500
  144. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
  145. package/src/resources/extensions/gsd/debug-logger.ts +11 -0
  146. package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
  147. package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
  148. package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
  149. package/src/resources/extensions/gsd/native-git-bridge.ts +9 -0
  150. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  151. package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
  152. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
  153. package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
  154. package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
  155. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
  156. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
  157. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
  158. package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
  159. package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
  160. package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
  161. package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
  162. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
  163. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
  164. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
  165. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
  166. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
  167. package/dist/web/standalone/.next/server/chunks/678.js +0 -2
  168. /package/dist/web/standalone/.next/static/{vAecbJ3K9eO213bAxU8Mi → h38jfi0dnRY0y3hbyBszg}/_buildManifest.js +0 -0
  169. /package/dist/web/standalone/.next/static/{vAecbJ3K9eO213bAxU8Mi → h38jfi0dnRY0y3hbyBszg}/_ssgManifest.js +0 -0
@@ -205,12 +205,17 @@ export async function preDispatchHealthGate(basePath) {
205
205
  catch {
206
206
  // Non-fatal — dispatch continues without STATE.md if rebuild fails
207
207
  }
208
+ // #442 Phase 1.7: resolve repo-ness once per gate. ensureWorkspaceGitReady
209
+ // has already run, and nothing below initializes/deinitializes the repo, so
210
+ // the two downstream blocks (integration-branch check, stale-changes
211
+ // snapshot) can share one nativeIsRepo result instead of querying twice.
212
+ const isRepo = nativeIsRepo(basePath);
208
213
  // ── Integration branch existence check ──
209
214
  // If the active milestone's recorded integration branch no longer exists in
210
215
  // git, the merge-back at the end of the milestone will fail. Block dispatch
211
216
  // now to surface this before work is lost.
212
217
  try {
213
- if (nativeIsRepo(basePath)) {
218
+ if (isRepo) {
214
219
  const state = await deriveState(basePath);
215
220
  if (state.activeMilestone) {
216
221
  const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
@@ -231,7 +236,7 @@ export async function preDispatchHealthGate(basePath) {
231
236
  // If the working tree is dirty and no commit has happened recently,
232
237
  // create a safety snapshot so work isn't lost if the next unit crashes.
233
238
  try {
234
- if (nativeIsRepo(basePath)) {
239
+ if (isRepo) {
235
240
  const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
236
241
  // `git.snapshots: false` is the canonical toggle that disables WIP
237
242
  // snapshot commits — honour it before touching the threshold path (#4420).
@@ -8,14 +8,14 @@
8
8
  //
9
9
  // Critical invariant: rendered markdown must round-trip through
10
10
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
11
- import { readFileSync, existsSync, mkdirSync } from "node:fs";
11
+ import { readFileSync, existsSync, mkdirSync, statSync } from "node:fs";
12
12
  import { logWarning } from "./workflow-logger.js";
13
13
  import { isClosedStatus } from "./status-guards.js";
14
14
  import { dirname, join, relative } from "node:path";
15
- import { createRequire } from "node:module";
16
15
  import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, insertArtifact, getGateResults, } from "./gsd-db.js";
17
16
  import { resolveDir, resolveFile, resolveSliceFile, resolveSlicePath, gsdProjectionRoot, gsdRoot, buildMilestoneFileName, buildTaskFileName, buildSliceFileName, } from "./paths.js";
18
- import { saveFile, clearParseCache } from "./files.js";
17
+ import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
18
+ import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
19
19
  import { invalidateStateCache } from "./state.js";
20
20
  import { clearPathCache } from "./paths.js";
21
21
  // ─── Helpers ──────────────────────────────────────────────────────────────
@@ -562,34 +562,35 @@ export async function renderAllFromDb(basePath) {
562
562
  }
563
563
  return result;
564
564
  }
565
- /**
566
- * Detect stale renders by comparing DB state against file content.
567
- *
568
- * Checks:
569
- * 1. Roadmap checkbox states vs DB slice statuses
570
- * 2. Plan checkbox states vs DB task statuses
571
- * 3. Missing SUMMARY.md files for complete tasks with full_summary_md
572
- * 4. Missing SUMMARY.md/UAT.md files for complete slices with content
573
- *
574
- * Returns a list of stale entries with file path and reason.
575
- * Logs to stderr when stale files are detected.
576
- */
577
- export function detectStaleRenders(basePath) {
578
- // Lazy-load parsers — intentional disk-vs-DB comparison requires parsers
579
- const _require = createRequire(import.meta.url);
580
- let parseRoadmap, parsePlan;
565
+ const _projectionParseCache = new Map();
566
+ registerCacheClearCallback(() => _projectionParseCache.clear());
567
+ function parseProjectionByIdentity(path, parse) {
568
+ let st = null;
581
569
  try {
582
- // Prefer compiled JS for packaged/runtime installs; TS exists only in source/dev contexts.
583
- const m = _require("./parsers-legacy.js");
584
- parseRoadmap = m.parseRoadmap;
585
- parsePlan = m.parsePlan;
570
+ st = statSync(path);
586
571
  }
587
- catch (e) {
588
- logWarning("renderer", `parsers-legacy.js require failed, falling back to .ts: ${e.message}`);
589
- const m = _require("./parsers-legacy.ts");
590
- parseRoadmap = m.parseRoadmap;
591
- parsePlan = m.parsePlan;
572
+ catch {
573
+ st = null;
574
+ }
575
+ if (st) {
576
+ const hit = _projectionParseCache.get(path);
577
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
578
+ return hit.parsed;
579
+ }
580
+ const parsed = parse(readFileSync(path, "utf-8"));
581
+ _projectionParseCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, parsed });
582
+ return parsed;
592
583
  }
584
+ // stat failed (e.g. file vanished between existsSync and here) — fall back to
585
+ // the original plain read+parse so error handling is unchanged.
586
+ return parse(readFileSync(path, "utf-8"));
587
+ }
588
+ export function detectStaleRenders(basePath) {
589
+ // parseRoadmap/parsePlan are statically imported (#442 Phase 1.4): the
590
+ // per-call createRequire("./parsers-legacy") that used to live here ran on
591
+ // every dispatch. The static `./parsers-legacy.js` specifier resolves in
592
+ // both packaged (.js) and source (.ts via the strip-types loader) contexts —
593
+ // the same form a dozen other modules already use.
593
594
  const stale = [];
594
595
  const milestones = getAllMilestones();
595
596
  for (const milestone of milestones) {
@@ -598,8 +599,7 @@ export function detectStaleRenders(basePath) {
598
599
  const roadmapPath = resolveRoadmapProjectionPath(basePath, milestone.id);
599
600
  if (existsSync(roadmapPath)) {
600
601
  try {
601
- const content = readFileSync(roadmapPath, "utf-8");
602
- const parsed = parseRoadmap(content);
602
+ const parsed = parseProjectionByIdentity(roadmapPath, parseRoadmap);
603
603
  for (const slice of slices) {
604
604
  const isCompleteInDb = isClosedStatus(slice.status);
605
605
  const roadmapSlice = parsed.slices.find((s) => s.id === slice.id);
@@ -630,8 +630,7 @@ export function detectStaleRenders(basePath) {
630
630
  const planPath = resolveSliceFile(basePath, milestone.id, slice.id, "PLAN");
631
631
  if (planPath && existsSync(planPath)) {
632
632
  try {
633
- const content = readFileSync(planPath, "utf-8");
634
- const parsed = parsePlan(content);
633
+ const parsed = parseProjectionByIdentity(planPath, parsePlan);
635
634
  for (const task of tasks) {
636
635
  const isDoneInDb = isClosedStatus(task.status);
637
636
  const planTask = parsed.tasks.find((t) => t.id === task.id);
@@ -1,4 +1,5 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import { resolve } from "node:path";
3
4
  import { resolveModelMcpConfig } from "./preferences-mcp.js";
4
5
  function isRecord(value) {
@@ -88,6 +89,11 @@ export function discoverBrowserMcpServerName(projectDir) {
88
89
  export function discoverMcpServerNames(projectDir) {
89
90
  return discoverMcpServers(projectDir).map((server) => server.name);
90
91
  }
92
+ export function discoverUserMcpServerNames() {
93
+ const userSettingsPath = resolve(homedir(), ".claude", "settings.json");
94
+ const userSettings = readJsonFile(userSettingsPath, true);
95
+ return collectServerEntries(userSettings?.mcpServers).map((s) => s.name);
96
+ }
91
97
  export function computeMcpDisallowedTools(modelId, mcpConfig, discoveredServers, workflowServerName) {
92
98
  if (!mcpConfig)
93
99
  return [];
@@ -11,6 +11,7 @@ import { GSDError, GSD_GIT_ERROR } from "./errors.js";
11
11
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
12
12
  import { getErrorMessage } from "./error-utils.js";
13
13
  import { isInfrastructureError } from "./auto/infra-errors.js";
14
+ import { debugCount } from "./debug-logger.js";
14
15
  // Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
15
16
  // caller explicitly opts into the native helper.
16
17
  const NATIVE_GSD_GIT_ENABLED = process.env.GSD_ENABLE_NATIVE_GSD_GIT === "1";
@@ -40,6 +41,8 @@ function loadNative() {
40
41
  // ─── Fallback Helpers ──────────────────────────────────────────────────────
41
42
  /** Run a git command via execFileSync. Returns trimmed stdout. */
42
43
  function gitExec(basePath, args, allowFailure = false) {
44
+ // Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
45
+ debugCount("gitInvocations");
43
46
  try {
44
47
  return execFileSync("git", args, {
45
48
  cwd: basePath,
@@ -64,6 +67,8 @@ function isRetryableGitError(err) {
64
67
  return code !== null && TRANSIENT_GIT_RETRY_CODES.has(code);
65
68
  }
66
69
  function execGitFileSyncWithRetry(basePath, args, options) {
70
+ // Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
71
+ debugCount("gitInvocations");
67
72
  try {
68
73
  return execFileSync("git", args, {
69
74
  cwd: basePath,
@@ -77,6 +82,8 @@ function execGitFileSyncWithRetry(basePath, args, options) {
77
82
  if (!isRetryableGitError(err))
78
83
  throw err;
79
84
  sleepSync(GIT_RETRY_DELAY_MS);
85
+ // Retry is a second physical shell-out — count it too.
86
+ debugCount("gitInvocations");
80
87
  return execFileSync("git", args, {
81
88
  cwd: basePath,
82
89
  stdio: ["ignore", "pipe", "pipe"],
@@ -88,6 +95,8 @@ function execGitFileSyncWithRetry(basePath, args, options) {
88
95
  }
89
96
  /** Run a git command via execFileSync. Returns trimmed stdout. */
90
97
  function gitFileExec(basePath, args, allowFailure = false) {
98
+ // Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
99
+ debugCount("gitInvocations");
91
100
  try {
92
101
  return execFileSync("git", args, {
93
102
  cwd: basePath,
@@ -38,7 +38,7 @@ If slice research is inlined, trust its architectural findings, but verify every
38
38
 
39
39
  1. If requirements are preloaded, identify owned and supporting Active requirements.
40
40
  2. Call `memory_query` with keywords from the slice title and source files.
41
- 3. Read `{{planTemplatePath}}` and `{{taskPlanTemplatePath}}`.
41
+ 3. Use the inlined Output Template sections already present in this prompt. Do not read template files from disk.
42
42
  4. {{skillActivation}} Record expected executor skills in each task plan's `skills_used` frontmatter.
43
43
  5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
44
44
  6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
@@ -6,7 +6,12 @@
6
6
  const TEMPLATE_TOKEN_RE = /\{\{[^}]+\}\}/;
7
7
  const H2_RE = /^##\s+(.+)$/gm;
8
8
  const H3_RE = /^###\s+(.+)$/gm;
9
- const MILESTONE_LINE_RE = /^-\s+\[([ x])\]\s+(M\d{3}):\s+(.+?)\s+(?:—|--|-)\s+(.+)$/gm;
9
+ // A milestone line is single-line by construction. Every inter-token gap uses
10
+ // horizontal-whitespace classes (`[^\S\n]`) rather than `\s`, because `\s`
11
+ // matches newlines: a line missing a valid separator would otherwise let the
12
+ // `\s+(?:—|--|-)\s+` clause "bridge" onto the NEXT bullet's `- `, consuming it
13
+ // as the separator and silently swallowing the following well-formed milestone.
14
+ const MILESTONE_LINE_RE = /^-[^\S\n]+\[([ x])\][^\S\n]+(M\d{3}):[^\S\n]+(.+?)[^\S\n]+(?:—|--|-)[^\S\n]+(.+)$/gm;
10
15
  const SLICE_HEADER_RE = /^###\s+(S\d{2})\s*(?:—|--|-)\s+(.+)$/m;
11
16
  const REQUIREMENT_HEADER_RE = /^###\s+(R\d{3})\s*(?:—|--|-)\s+(.+)$/m;
12
17
  function splitH2Sections(content) {
@@ -250,7 +250,27 @@ function detectDiskSliceIdDivergenceForMilestone(basePath, milestoneId) {
250
250
  }
251
251
  return drifts;
252
252
  }
253
- export function detectArtifactDbDrift(_state, ctx) {
253
+ // #442 Phase 1.6: the three artifact/DB drift handlers (disk-slice-id,
254
+ // artifact-db-status, completed-milestone-reopened) each call
255
+ // detectArtifactDbDrift and then filter for their own kind — so the full
256
+ // milestone→slice→task walk + artifact SQL + disk scan would run THREE times
257
+ // per detection pass and discard 2/3 of the work. Memoize the result per
258
+ // DriftContext so the three handlers share one computation. The key is the
259
+ // ctx object, which detectAllDrift rebuilds for every pass (and which is
260
+ // unreferenced once the pass ends, so the WeakMap entry is GC'd) — DB/disk
261
+ // state is immutable within a single pass (repairs run only after detection),
262
+ // so this is behavior-preserving. A fresh ctx (e.g. the maintenance command's
263
+ // inline { basePath, state }) always recomputes.
264
+ const _artifactDbDriftMemo = new WeakMap();
265
+ export function detectArtifactDbDrift(state, ctx) {
266
+ const cached = _artifactDbDriftMemo.get(ctx);
267
+ if (cached)
268
+ return cached;
269
+ const computed = computeArtifactDbDrift(state, ctx);
270
+ _artifactDbDriftMemo.set(ctx, computed);
271
+ return computed;
272
+ }
273
+ function computeArtifactDbDrift(_state, ctx) {
254
274
  if (!isDbAvailable())
255
275
  return [];
256
276
  const drifts = [];
@@ -3,7 +3,7 @@
3
3
  import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
4
4
  import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-milestone.js";
5
5
  import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, shouldBlockRootArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
6
- import { getActiveRequirements, insertMilestone, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertAssessment, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
6
+ import { getActiveRequirements, getAllMilestones, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertMilestone, insertAssessment, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
7
7
  import { GATE_REGISTRY } from "../gate-registry.js";
8
8
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
9
9
  import { clearPathCache, relSliceFile, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
@@ -74,6 +74,103 @@ function registerProjectMilestoneSequence(content) {
74
74
  }
75
75
  return registered;
76
76
  }
77
+ /**
78
+ * Best-effort recovery of the human one-liner for each milestone id from a
79
+ * (possibly malformed) Milestone Sequence body. Deliberately lenient: tolerates
80
+ * any separator the canonical MILESTONE_LINE_RE rejects (en-dash, " : ", a
81
+ * missing checkbox, etc.) so a model formatting slip does not discard the prose.
82
+ */
83
+ function recoverMilestoneTails(sequenceBody) {
84
+ const out = new Map();
85
+ const lenient = /^\s*(?:-\s*)?(?:\[[ xX]\]\s*)?(M\d{3})\b\s*[:.\-–—]*\s*(.*)$/;
86
+ for (const rawLine of sequenceBody.split("\n")) {
87
+ const m = rawLine.match(lenient);
88
+ if (m)
89
+ out.set(m[1], m[2].trim());
90
+ }
91
+ return out;
92
+ }
93
+ function firstSentence(text) {
94
+ const trimmed = text.trim();
95
+ if (!trimmed)
96
+ return "";
97
+ const idx = trimmed.search(/[.!?](\s|$)/);
98
+ return (idx >= 0 ? trimmed.slice(0, idx + 1) : trimmed).trim();
99
+ }
100
+ /** Render one canonical, parseable milestone line for the given DB row. */
101
+ function renderMilestoneLine(m, recoveredTail) {
102
+ const done = m.status === "complete";
103
+ let oneLiner = recoveredTail;
104
+ // The recovered tail often still carries the title (e.g. "Foo — bar" or
105
+ // "Foo : bar"). Strip a leading repetition of the title, then any separator.
106
+ if (oneLiner.toLowerCase().startsWith(m.title.toLowerCase())) {
107
+ oneLiner = oneLiner.slice(m.title.length).replace(/^\s*[:.\-–—]+\s*/, "").trim();
108
+ }
109
+ else {
110
+ const sep = oneLiner.match(/\s+(?:—|–|--|-|:)\s+/);
111
+ if (sep && sep.index !== undefined)
112
+ oneLiner = oneLiner.slice(sep.index + sep[0].length).trim();
113
+ }
114
+ // MILESTONE_LINE_RE requires non-empty prose after the separator.
115
+ if (!oneLiner)
116
+ oneLiner = firstSentence(m.vision) || (done ? "Completed." : "Planned.");
117
+ return `- [${done ? "x" : " "}] ${m.id}: ${m.title} — ${oneLiner}`;
118
+ }
119
+ /**
120
+ * Rebuild the "## Milestone Sequence" section from authoritative DB rows when a
121
+ * model-authored PROJECT.md projection parsed to zero milestone lines but the DB
122
+ * already holds milestones. The DB is the source of truth (markdown is a
123
+ * projection), so this repairs the projection rather than failing the save.
124
+ * Preserves a leading HTML comment in the section and recovers one-liners
125
+ * best-effort. The returned content parses cleanly under MILESTONE_LINE_RE.
126
+ */
127
+ function rebuildMilestoneSequenceSection(content, milestones) {
128
+ const lines = content.split("\n");
129
+ const headerIdx = lines.findIndex(l => /^##\s+Milestone Sequence\s*$/.test(l));
130
+ const canonicalLines = (() => {
131
+ // Recover tails from the existing (malformed) body when the section exists.
132
+ let body = "";
133
+ if (headerIdx !== -1) {
134
+ let end = headerIdx + 1;
135
+ while (end < lines.length && !/^##\s+/.test(lines[end]))
136
+ end++;
137
+ body = lines.slice(headerIdx + 1, end).join("\n");
138
+ }
139
+ const tails = recoverMilestoneTails(body);
140
+ return milestones.map(m => renderMilestoneLine(m, tails.get(m.id) ?? ""));
141
+ })();
142
+ if (headerIdx === -1) {
143
+ // No section at all — append a fresh, canonical one.
144
+ const sep = content.endsWith("\n") ? "" : "\n";
145
+ return `${content}${sep}\n## Milestone Sequence\n\n${canonicalLines.join("\n")}\n`;
146
+ }
147
+ let bodyEnd = headerIdx + 1;
148
+ while (bodyEnd < lines.length && !/^##\s+/.test(lines[bodyEnd]))
149
+ bodyEnd++;
150
+ const existingBody = lines.slice(headerIdx + 1, bodyEnd);
151
+ // Preserve a contiguous leading HTML comment block (the "Check off…" hint).
152
+ let i = 0;
153
+ while (i < existingBody.length && existingBody[i].trim() === "")
154
+ i++;
155
+ const preserved = [];
156
+ if (i < existingBody.length && existingBody[i].trim().startsWith("<!--")) {
157
+ while (i < existingBody.length) {
158
+ preserved.push(existingBody[i]);
159
+ const closed = existingBody[i].includes("-->");
160
+ i++;
161
+ if (closed)
162
+ break;
163
+ }
164
+ }
165
+ return [
166
+ ...lines.slice(0, headerIdx + 1),
167
+ "",
168
+ ...(preserved.length ? [...preserved, ""] : []),
169
+ ...canonicalLines,
170
+ "",
171
+ ...lines.slice(bodyEnd),
172
+ ].join("\n");
173
+ }
77
174
  async function mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, content) {
78
175
  const contract = resolveGsdPathContract(basePath);
79
176
  if (!contract.worktreeGsd)
@@ -192,6 +289,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
192
289
  }, basePath);
193
290
  await mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, contentToSave);
194
291
  let registeredMilestones = [];
292
+ let milestoneSequenceSelfHealed = false;
195
293
  if (params.artifact_type === "PROJECT") {
196
294
  try {
197
295
  registeredMilestones = registerProjectMilestoneSequence(contentToSave);
@@ -227,28 +325,78 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
227
325
  };
228
326
  }
229
327
  if (registeredMilestones.length === 0) {
230
- logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
328
+ const existingMilestones = getAllMilestones();
329
+ if (existingMilestones.length === 0) {
330
+ // Genuine first-save failure: no milestones parsed AND none in the DB.
331
+ // /gsd really would report "No Active Milestone" — hard-fail so the
332
+ // caller rewrites the sequence before proceeding.
333
+ logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
334
+ tool: "gsd_summary_save",
335
+ });
336
+ // PROJECT.md was persisted; invalidate so subsequent reads see the new
337
+ // artifacts row even though no milestones registered.
338
+ invalidateStateCache();
339
+ return {
340
+ content: [{
341
+ type: "text",
342
+ text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
343
+ `so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
344
+ `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
345
+ `\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
346
+ }],
347
+ details: {
348
+ operation: "save_summary",
349
+ path: relativePath,
350
+ artifact_type: params.artifact_type,
351
+ error: "milestone_registration_empty_parse",
352
+ },
353
+ isError: true,
354
+ };
355
+ }
356
+ // Existing DB rows mean this is projection drift, not data loss. Rebuild
357
+ // the section from DB state and re-persist a parseable projection.
358
+ logWarning("tool", `gsd_summary_save: PROJECT.md parsed zero milestone lines but DB has ${existingMilestones.length} — rebuilding Milestone Sequence from DB (projection self-heal)`, {
231
359
  tool: "gsd_summary_save",
360
+ path: relativePath,
232
361
  });
233
- // PROJECT.md was persisted; invalidate so subsequent reads see the new
234
- // artifacts row even though no milestones registered.
235
- invalidateStateCache();
236
- return {
237
- content: [{
238
- type: "text",
239
- text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
240
- `so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
241
- `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
242
- `\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
243
- }],
244
- details: {
245
- operation: "save_summary",
362
+ try {
363
+ const healed = rebuildMilestoneSequenceSection(contentToSave, existingMilestones);
364
+ await saveArtifactToDb({ path: relativePath, artifact_type: params.artifact_type, content: healed }, basePath);
365
+ await mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, healed);
366
+ const healedRegisteredMilestones = registerProjectMilestoneSequence(healed);
367
+ if (healedRegisteredMilestones.length === 0) {
368
+ throw new Error("self-healed PROJECT.md still parsed zero milestone lines");
369
+ }
370
+ registeredMilestones = healedRegisteredMilestones;
371
+ milestoneSequenceSelfHealed = true;
372
+ }
373
+ catch (healErr) {
374
+ const msg = healErr instanceof Error ? healErr.message : String(healErr);
375
+ logError("tool", `gsd_summary_save: Milestone Sequence self-heal failed: ${msg}`, {
376
+ tool: "gsd_summary_save",
246
377
  path: relativePath,
247
- artifact_type: params.artifact_type,
248
- error: "milestone_registration_empty_parse",
249
- },
250
- isError: true,
251
- };
378
+ error: msg,
379
+ });
380
+ invalidateStateCache();
381
+ return {
382
+ content: [{
383
+ type: "text",
384
+ text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
385
+ `and automatic DB-backed Milestone Sequence repair failed: ${msg}. ` +
386
+ `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
387
+ `\`- [ ] M001: <Title> — <One-liner>\`, then re-call gsd_summary_save(PROJECT).`,
388
+ }],
389
+ details: {
390
+ operation: "save_summary",
391
+ path: relativePath,
392
+ artifact_type: params.artifact_type,
393
+ error: "milestone_sequence_self_heal_failed",
394
+ self_heal_error: msg,
395
+ },
396
+ isError: true,
397
+ };
398
+ }
399
+ invalidateStateCache();
252
400
  }
253
401
  }
254
402
  if (params.artifact_type === "CONTEXT" && !params.task_id) {
@@ -271,6 +419,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
271
419
  artifact_type: params.artifact_type,
272
420
  content_source: contentSource,
273
421
  ...(registeredMilestones.length > 0 ? { registeredMilestones } : {}),
422
+ ...(milestoneSequenceSelfHealed ? { milestoneSequenceSelfHealed: true } : {}),
274
423
  },
275
424
  };
276
425
  }
@@ -1 +1 @@
1
- vAecbJ3K9eO213bAxU8Mi
1
+ h38jfi0dnRY0y3hbyBszg
@@ -4,12 +4,12 @@
4
4
  "/api/boot/route": "/api/boot",
5
5
  "/api/bridge-terminal/input/route": "/api/bridge-terminal/input",
6
6
  "/api/bridge-terminal/resize/route": "/api/bridge-terminal/resize",
7
- "/api/bridge-terminal/stream/route": "/api/bridge-terminal/stream",
8
7
  "/api/browse-directories/route": "/api/browse-directories",
9
- "/api/captures/route": "/api/captures",
10
8
  "/api/dev-mode/route": "/api/dev-mode",
9
+ "/api/bridge-terminal/stream/route": "/api/bridge-terminal/stream",
11
10
  "/api/cleanup/route": "/api/cleanup",
12
11
  "/api/doctor/route": "/api/doctor",
12
+ "/api/captures/route": "/api/captures",
13
13
  "/api/export-data/route": "/api/export-data",
14
14
  "/api/experimental/route": "/api/experimental",
15
15
  "/api/forensics/route": "/api/forensics",
@@ -19,11 +19,11 @@
19
19
  "/api/inspect/route": "/api/inspect",
20
20
  "/api/knowledge/route": "/api/knowledge",
21
21
  "/api/live-state/route": "/api/live-state",
22
- "/api/files/route": "/api/files",
23
22
  "/api/mcp-connections/route": "/api/mcp-connections",
24
23
  "/api/notifications/route": "/api/notifications",
25
- "/api/preferences/route": "/api/preferences",
24
+ "/api/files/route": "/api/files",
26
25
  "/api/onboarding/route": "/api/onboarding",
26
+ "/api/preferences/route": "/api/preferences",
27
27
  "/api/recovery/route": "/api/recovery",
28
28
  "/api/projects/route": "/api/projects",
29
29
  "/api/session/browser/route": "/api/session/browser",
@@ -32,11 +32,11 @@
32
32
  "/api/settings-data/route": "/api/settings-data",
33
33
  "/api/session/manage/route": "/api/session/manage",
34
34
  "/api/shutdown/route": "/api/shutdown",
35
- "/api/skill-health/route": "/api/skill-health",
36
35
  "/api/remote-questions/route": "/api/remote-questions",
36
+ "/api/skill-health/route": "/api/skill-health",
37
37
  "/api/steer/route": "/api/steer",
38
- "/api/switch-root/route": "/api/switch-root",
39
38
  "/api/terminal/input/route": "/api/terminal/input",
39
+ "/api/switch-root/route": "/api/switch-root",
40
40
  "/api/terminal/resize/route": "/api/terminal/resize",
41
41
  "/api/terminal/sessions/route": "/api/terminal/sessions",
42
42
  "/api/terminal/stream/route": "/api/terminal/stream",
@@ -4,8 +4,8 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/vAecbJ3K9eO213bAxU8Mi/_buildManifest.js",
8
- "static/vAecbJ3K9eO213bAxU8Mi/_ssgManifest.js"
7
+ "static/h38jfi0dnRY0y3hbyBszg/_buildManifest.js",
8
+ "static/h38jfi0dnRY0y3hbyBszg/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
11
  "static/chunks/webpack-dda80a1ef5587410.js",
@@ -78,8 +78,8 @@
78
78
  "dynamicRoutes": {},
79
79
  "notFoundRoutes": [],
80
80
  "preview": {
81
- "previewModeId": "bb19eaf9ed3f1170a1734ebde9918c4d",
82
- "previewModeSigningKey": "a422606b81a06d96ebe85b1e3b44948168227e45ee431592f4d553ef14c7b172",
83
- "previewModeEncryptionKey": "e54b879f1b51c84f4271cbd36035c54779ad0389aa303a65f27679b8c7a15d08"
81
+ "previewModeId": "30286f2ea254c7b62e2eccbd95bda1f9",
82
+ "previewModeSigningKey": "b6ad03023e97ec79fc6584ed47293aabc27d15552a29811540288010912ec929",
83
+ "previewModeEncryptionKey": "fbee4e739613703bdcbfc9a2d5bdb491f48806955998ccfcf5a201924ea58a34"
84
84
  }
85
85
  }
@@ -1 +1 @@
1
- <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-dda80a1ef5587410.js"/><script src="/_next/static/chunks/51aff721-df603e9695992f6b.js" async=""></script><script src="/_next/static/chunks/686-9424f271d9ce5d07.js" async=""></script><script src="/_next/static/chunks/main-app-90d1d8d5e5d2dc6b.js" async=""></script><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-dda80a1ef5587410.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[25020,[],\"\"]\n3:I[81130,[],\"\"]\n4:I[84649,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[84649,[],\"ViewportBoundary\"]\na:I[84649,[],\"MetadataBoundary\"]\nc:I[45336,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],null]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"vAecbJ3K9eO213bAxU8Mi\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>
1
+ <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-dda80a1ef5587410.js"/><script src="/_next/static/chunks/51aff721-df603e9695992f6b.js" async=""></script><script src="/_next/static/chunks/686-9424f271d9ce5d07.js" async=""></script><script src="/_next/static/chunks/main-app-90d1d8d5e5d2dc6b.js" async=""></script><title>500: This page couldn’t load</title><style>:root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }</style><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;display:flex;align-items:center;justify-content:center"><div style="margin-top:-32px;max-width:325px;padding:32px 28px;text-align:left"><svg width="32" height="32" viewBox="-0.2 -1.5 32 32" fill="none" style="margin-bottom:24px"><path d="M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z" fill="var(--next-error-title)"></path></svg><h1 style="font-size:24px;font-weight:500;letter-spacing:-0.02em;line-height:32px;margin:0 0 12px 0;color:var(--next-error-title)">This page couldn’t load</h1><p style="font-size:14px;font-weight:400;line-height:21px;margin:0 0 20px 0;color:var(--next-error-message)">A server error occurred. Reload to try again.</p><form style="margin:0"><button type="submit" style="display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;font-size:14px;font-weight:500;line-height:20px;border-radius:6px;cursor:pointer;color:var(--next-error-btn-text);background:var(--next-error-btn-bg);border:var(--next-error-btn-border)">Reload</button></form></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-dda80a1ef5587410.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[25020,[],\"\"]\n3:I[81130,[],\"\"]\n4:I[84649,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[84649,[],\"ViewportBoundary\"]\na:I[84649,[],\"MetadataBoundary\"]\nc:I[45336,[],\"default\",1]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"title\",null,{\"children\":\"500: This page couldn’t load\"}],[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\":root {--next-error-bg: #fff;--next-error-text: #171717;--next-error-title: #171717;--next-error-message: #171717;--next-error-digest: #666666;--next-error-btn-text: #fff;--next-error-btn-bg: #171717;--next-error-btn-border: none;--next-error-btn-secondary-text: #171717;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(0,0,0,0.08);}@media (prefers-color-scheme: dark) {:root {--next-error-bg: #0a0a0a;--next-error-text: #ededed;--next-error-title: #ededed;--next-error-message: #ededed;--next-error-digest: #a0a0a0;--next-error-btn-text: #0a0a0a;--next-error-btn-bg: #ededed;--next-error-btn-border: none;--next-error-btn-secondary-text: #ededed;--next-error-btn-secondary-bg: transparent;--next-error-btn-secondary-border: 1px solid rgba(255,255,255,0.14);}}body { margin: 0; color: var(--next-error-text); background: var(--next-error-bg); }\"}}]]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"display\":\"flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"marginTop\":\"-32px\",\"maxWidth\":\"325px\",\"padding\":\"32px 28px\",\"textAlign\":\"left\"},\"children\":[[\"$\",\"svg\",null,{\"width\":\"32\",\"height\":\"32\",\"viewBox\":\"-0.2 -1.5 32 32\",\"fill\":\"none\",\"style\":{\"marginBottom\":\"24px\"},\"children\":[\"$\",\"path\",null,{\"d\":\"M16.9328 0C18.0839 0.000116771 19.1334 0.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C0.527541 29 -0.439628 27.4588 0.201371 26.1309L11.9973 1.69531C12.4979 0.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z\",\"fill\":\"var(--next-error-title)\"}]}],[\"$\",\"h1\",null,{\"style\":{\"fontSize\":\"24px\",\"fontWeight\":500,\"letterSpacing\":\"-0.02em\",\"lineHeight\":\"32px\",\"margin\":\"0 0 12px 0\",\"color\":\"var(--next-error-title)\"},\"children\":\"This page couldn’t load\"}],[\"$\",\"p\",null,{\"style\":{\"fontSize\":\"14px\",\"fontWeight\":400,\"lineHeight\":\"21px\",\"margin\":\"0 0 20px 0\",\"color\":\"var(--next-error-message)\"},\"children\":\"A server error occurred. Reload to try again.\"}],[\"$\",\"form\",null,{\"style\":{\"margin\":0},\"children\":[\"$\",\"button\",null,{\"type\":\"submit\",\"style\":{\"display\":\"inline-flex\",\"alignItems\":\"center\",\"justifyContent\":\"center\",\"height\":\"32px\",\"padding\":\"0 12px\",\"fontSize\":\"14px\",\"fontWeight\":500,\"lineHeight\":\"20px\",\"borderRadius\":\"6px\",\"cursor\":\"pointer\",\"color\":\"var(--next-error-btn-text)\",\"background\":\"var(--next-error-btn-bg)\",\"border\":\"var(--next-error-btn-border)\"},\"children\":\"Reload\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$@7\"]},null,false,\"$@7\"],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],null]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"h38jfi0dnRY0y3hbyBszg\"}\n"])</script><script>self.__next_f.push([1,"d:[]\n7:\"$Wd\"\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\nb:[]\n"])</script></body></html>