gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.98b44dc

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 (217) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/extension-registry.js +2 -2
  4. package/dist/remote-questions-config.js +2 -2
  5. package/dist/resource-loader.js +34 -1
  6. package/dist/resources/extensions/browser-tools/index.js +3 -1
  7. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  8. package/dist/resources/extensions/env-utils.js +29 -0
  9. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  10. package/dist/resources/extensions/github-sync/cli.js +284 -0
  11. package/dist/resources/extensions/github-sync/index.js +73 -0
  12. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  13. package/dist/resources/extensions/github-sync/sync.js +424 -0
  14. package/dist/resources/extensions/github-sync/templates.js +118 -0
  15. package/dist/resources/extensions/github-sync/types.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  18. package/dist/resources/extensions/gsd/auto-loop.js +636 -594
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  20. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  21. package/dist/resources/extensions/gsd/auto-start.js +7 -1
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
  23. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  24. package/dist/resources/extensions/gsd/auto.js +143 -96
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  27. package/dist/resources/extensions/gsd/commands.js +4 -2
  28. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  29. package/dist/resources/extensions/gsd/detection.js +1 -2
  30. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  31. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  32. package/dist/resources/extensions/gsd/doctor.js +20 -1
  33. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  34. package/dist/resources/extensions/gsd/export.js +1 -1
  35. package/dist/resources/extensions/gsd/files.js +48 -9
  36. package/dist/resources/extensions/gsd/forensics.js +1 -1
  37. package/dist/resources/extensions/gsd/git-service.js +30 -12
  38. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  39. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  40. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  41. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  42. package/dist/resources/extensions/gsd/index.js +24 -20
  43. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  44. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  46. package/dist/resources/extensions/gsd/paths.js +3 -0
  47. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  48. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  49. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  50. package/dist/resources/extensions/gsd/preferences.js +22 -11
  51. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  52. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  55. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  56. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  59. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  62. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  63. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  68. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  69. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  72. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  73. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  74. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  75. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  76. package/dist/resources/extensions/gsd/state.js +42 -23
  77. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  78. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  79. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  80. package/dist/resources/extensions/mcp-client/index.js +14 -1
  81. package/dist/resources/extensions/remote-questions/status.js +4 -1
  82. package/dist/resources/extensions/remote-questions/store.js +4 -1
  83. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  84. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  85. package/dist/resources/extensions/subagent/isolation.js +2 -1
  86. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  87. package/package.json +1 -1
  88. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  89. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  90. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  91. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  93. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  95. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  97. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  99. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/index.js +1 -1
  101. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  102. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  103. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  104. package/packages/pi-coding-agent/src/index.ts +1 -0
  105. package/src/resources/extensions/browser-tools/index.ts +3 -0
  106. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  107. package/src/resources/extensions/env-utils.ts +31 -0
  108. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  109. package/src/resources/extensions/github-sync/cli.ts +364 -0
  110. package/src/resources/extensions/github-sync/index.ts +93 -0
  111. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  112. package/src/resources/extensions/github-sync/sync.ts +556 -0
  113. package/src/resources/extensions/github-sync/templates.ts +183 -0
  114. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  115. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  116. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  117. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  118. package/src/resources/extensions/github-sync/types.ts +47 -0
  119. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  120. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  121. package/src/resources/extensions/gsd/auto-loop.ts +526 -545
  122. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  123. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  124. package/src/resources/extensions/gsd/auto-start.ts +11 -1
  125. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
  126. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  127. package/src/resources/extensions/gsd/auto.ts +139 -101
  128. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  129. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  130. package/src/resources/extensions/gsd/commands.ts +5 -3
  131. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  132. package/src/resources/extensions/gsd/detection.ts +2 -2
  133. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  134. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  135. package/src/resources/extensions/gsd/doctor.ts +22 -1
  136. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  137. package/src/resources/extensions/gsd/export.ts +1 -1
  138. package/src/resources/extensions/gsd/files.ts +51 -11
  139. package/src/resources/extensions/gsd/forensics.ts +1 -1
  140. package/src/resources/extensions/gsd/git-service.ts +44 -10
  141. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  142. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  143. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  144. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  145. package/src/resources/extensions/gsd/index.ts +24 -17
  146. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  147. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  148. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  149. package/src/resources/extensions/gsd/paths.ts +4 -0
  150. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  151. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  152. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  153. package/src/resources/extensions/gsd/preferences.ts +25 -11
  154. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  155. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  156. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  157. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  158. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  159. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  160. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  161. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  162. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  165. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  166. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  170. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  174. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  175. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  176. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  177. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  178. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  179. package/src/resources/extensions/gsd/state.ts +39 -21
  180. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  181. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  182. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  183. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  184. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  186. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  187. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  188. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  189. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  190. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  191. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  192. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  193. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  194. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  195. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  196. package/src/resources/extensions/gsd/types.ts +18 -1
  197. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  198. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  199. package/src/resources/extensions/mcp-client/index.ts +17 -1
  200. package/src/resources/extensions/remote-questions/status.ts +5 -1
  201. package/src/resources/extensions/remote-questions/store.ts +5 -1
  202. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  203. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  204. package/src/resources/extensions/subagent/isolation.ts +3 -1
  205. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  206. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  207. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  208. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  209. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  210. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  211. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  212. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  213. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  214. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  215. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  216. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  217. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -23,6 +23,12 @@ import * as _bundledYaml from "yaml";
23
23
  import * as _bundledMcpClient from "@modelcontextprotocol/sdk/client";
24
24
  import * as _bundledMcpStdio from "@modelcontextprotocol/sdk/client/stdio.js";
25
25
  import * as _bundledMcpStreamableHttp from "@modelcontextprotocol/sdk/client/streamableHttp.js";
26
+ import * as _bundledMcpSse from "@modelcontextprotocol/sdk/client/sse.js";
27
+ import * as _bundledMcpServer from "@modelcontextprotocol/sdk/server";
28
+ import * as _bundledMcpServerStdio from "@modelcontextprotocol/sdk/server/stdio.js";
29
+ import * as _bundledMcpServerSse from "@modelcontextprotocol/sdk/server/sse.js";
30
+ import * as _bundledMcpServerStreamableHttp from "@modelcontextprotocol/sdk/server/streamableHttp.js";
31
+ import * as _bundledMcpTypes from "@modelcontextprotocol/sdk/types.js";
26
32
  import { getAgentDir, isBunBinary } from "../../config.js";
27
33
  // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
28
34
  // avoiding a circular dependency. Extensions can import from @gsd/pi-coding-agent.
@@ -44,8 +50,11 @@ import type {
44
50
  ToolDefinition,
45
51
  } from "./types.js";
46
52
 
47
- /** Modules available to extensions via virtualModules (for compiled Bun binary) */
48
- const VIRTUAL_MODULES: Record<string, unknown> = {
53
+ /**
54
+ * Statically imported modules for Bun binary virtualModules.
55
+ * Maps specifier -> module object for subpaths that must be available in compiled binaries.
56
+ */
57
+ const STATIC_BUNDLED_MODULES: Record<string, unknown> = {
49
58
  "@sinclair/typebox": _bundledTypebox,
50
59
  "@gsd/pi-agent-core": _bundledPiAgentCore,
51
60
  "@gsd/pi-tui": _bundledPiTui,
@@ -58,6 +67,17 @@ const VIRTUAL_MODULES: Record<string, unknown> = {
58
67
  "@modelcontextprotocol/sdk/client/stdio.js": _bundledMcpStdio,
59
68
  "@modelcontextprotocol/sdk/client/streamableHttp": _bundledMcpStreamableHttp,
60
69
  "@modelcontextprotocol/sdk/client/streamableHttp.js": _bundledMcpStreamableHttp,
70
+ "@modelcontextprotocol/sdk/client/sse": _bundledMcpSse,
71
+ "@modelcontextprotocol/sdk/client/sse.js": _bundledMcpSse,
72
+ "@modelcontextprotocol/sdk/server": _bundledMcpServer,
73
+ "@modelcontextprotocol/sdk/server/stdio": _bundledMcpServerStdio,
74
+ "@modelcontextprotocol/sdk/server/stdio.js": _bundledMcpServerStdio,
75
+ "@modelcontextprotocol/sdk/server/sse": _bundledMcpServerSse,
76
+ "@modelcontextprotocol/sdk/server/sse.js": _bundledMcpServerSse,
77
+ "@modelcontextprotocol/sdk/server/streamableHttp": _bundledMcpServerStreamableHttp,
78
+ "@modelcontextprotocol/sdk/server/streamableHttp.js": _bundledMcpServerStreamableHttp,
79
+ "@modelcontextprotocol/sdk/types": _bundledMcpTypes,
80
+ "@modelcontextprotocol/sdk/types.js": _bundledMcpTypes,
61
81
  // Aliases for external PI ecosystem packages that import from the original scope
62
82
  "@mariozechner/pi-agent-core": _bundledPiAgentCore,
63
83
  "@mariozechner/pi-tui": _bundledPiTui,
@@ -66,9 +86,198 @@ const VIRTUAL_MODULES: Record<string, unknown> = {
66
86
  "@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
67
87
  };
68
88
 
89
+ /** Modules available to extensions via virtualModules (for compiled Bun binary) */
90
+ const VIRTUAL_MODULES: Record<string, unknown> = { ...STATIC_BUNDLED_MODULES };
91
+
69
92
  const require = createRequire(import.meta.url);
70
93
  const EXTENSION_TIMING_ENABLED = process.env.GSD_STARTUP_TIMING === "1" || process.env.PI_TIMING === "1";
71
94
 
95
+ /**
96
+ * Bundled npm packages whose subpath exports should be auto-resolved for extensions.
97
+ * Each package listed here will have its `exports` field read from package.json,
98
+ * and all subpath exports will be registered as jiti aliases (Node.js mode) so that
99
+ * extensions can import any standard subpath without hitting jiti's CJS double-resolve bug.
100
+ */
101
+ const BUNDLED_PACKAGES_WITH_EXPORTS = [
102
+ "@modelcontextprotocol/sdk",
103
+ "yaml",
104
+ ];
105
+
106
+ /**
107
+ * Read a package's `exports` field and return alias entries mapping
108
+ * specifiers (e.g. `@modelcontextprotocol/sdk/server`) to resolved file paths.
109
+ *
110
+ * Handles:
111
+ * - Explicit subpath exports: `./client` -> `@pkg/client`
112
+ * - Wildcard exports (`./*`): scans the package's dist directory for actual files
113
+ * - Both `.js`-suffixed and bare specifiers for each subpath
114
+ */
115
+ function resolveSubpathExports(packageName: string): Record<string, string> {
116
+ const aliases: Record<string, string> = {};
117
+
118
+ let packageJsonPath: string;
119
+ try {
120
+ // Resolve the package's root directory via its package.json
121
+ packageJsonPath = require.resolve(`${packageName}/package.json`);
122
+ } catch {
123
+ // Package doesn't allow importing package.json via exports — find it manually
124
+ try {
125
+ const anyEntry = require.resolve(packageName);
126
+ // Walk up from the resolved entry to find package.json
127
+ let dir = path.dirname(anyEntry);
128
+ while (dir !== path.dirname(dir)) {
129
+ const candidate = path.join(dir, "package.json");
130
+ if (fs.existsSync(candidate)) {
131
+ try {
132
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
133
+ if (pkg.name === packageName) {
134
+ packageJsonPath = candidate;
135
+ break;
136
+ }
137
+ } catch {
138
+ // not valid JSON, keep walking
139
+ }
140
+ }
141
+ dir = path.dirname(dir);
142
+ }
143
+ } catch {
144
+ return aliases;
145
+ }
146
+ if (!packageJsonPath!) return aliases;
147
+ }
148
+
149
+ let pkg: { exports?: Record<string, unknown> };
150
+ try {
151
+ pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
152
+ } catch {
153
+ return aliases;
154
+ }
155
+
156
+ const exports = pkg.exports;
157
+ if (!exports || typeof exports !== "object") return aliases;
158
+
159
+ const packageDir = path.dirname(packageJsonPath);
160
+
161
+ for (const [subpath, target] of Object.entries(exports)) {
162
+ if (subpath === ".") continue; // Root export handled by static imports
163
+
164
+ // Handle wildcard exports like "./*"
165
+ if (subpath.includes("*")) {
166
+ resolveWildcardExports(packageName, packageDir, subpath, target, aliases);
167
+ continue;
168
+ }
169
+
170
+ // Explicit subpath: "./client" -> "@pkg/client"
171
+ const specifier = `${packageName}/${subpath.replace(/^\.\//, "")}`;
172
+
173
+ try {
174
+ const resolved = require.resolve(specifier);
175
+ aliases[specifier] = resolved;
176
+
177
+ // Add .js-suffixed variant if the specifier doesn't already end in .js
178
+ if (!specifier.endsWith(".js")) {
179
+ const jsSpecifier = `${specifier}.js`;
180
+ try {
181
+ const jsResolved = require.resolve(jsSpecifier);
182
+ aliases[jsSpecifier] = jsResolved;
183
+ } catch {
184
+ // .js variant doesn't resolve — that's fine
185
+ }
186
+ }
187
+
188
+ // Add bare variant (without .js) if it ends in .js
189
+ if (specifier.endsWith(".js")) {
190
+ const bareSpecifier = specifier.slice(0, -3);
191
+ try {
192
+ const bareResolved = require.resolve(bareSpecifier);
193
+ aliases[bareSpecifier] = bareResolved;
194
+ } catch {
195
+ // bare variant doesn't resolve — that's fine
196
+ }
197
+ }
198
+ } catch {
199
+ // Subpath doesn't resolve — skip it
200
+ }
201
+ }
202
+
203
+ return aliases;
204
+ }
205
+
206
+ /**
207
+ * Resolve wildcard export patterns (e.g. `./*`) by scanning the package's
208
+ * file structure to find all matching files and generate alias entries.
209
+ */
210
+ function resolveWildcardExports(
211
+ packageName: string,
212
+ packageDir: string,
213
+ subpathPattern: string,
214
+ target: unknown,
215
+ aliases: Record<string, string>,
216
+ ): void {
217
+ // Extract the target directory pattern from the export target
218
+ // e.g. { "require": "./dist/cjs/*" } -> "dist/cjs"
219
+ let targetDir: string | null = null;
220
+
221
+ if (typeof target === "string") {
222
+ targetDir = target.replace(/\/\*$/, "").replace(/^\.\//, "");
223
+ } else if (target && typeof target === "object") {
224
+ const targetObj = target as Record<string, unknown>;
225
+ // Prefer "require" for CJS compatibility with jiti, fall back to "import"
226
+ const resolved = targetObj.require ?? targetObj.import ?? targetObj.default;
227
+ if (typeof resolved === "string") {
228
+ targetDir = resolved.replace(/\/\*$/, "").replace(/^\.\//, "");
229
+ }
230
+ }
231
+
232
+ if (!targetDir) return;
233
+
234
+ const fullTargetDir = path.join(packageDir, targetDir);
235
+ if (!fs.existsSync(fullTargetDir)) return;
236
+
237
+ // Scan for .js files and generate specifiers
238
+ const subpathPrefix = subpathPattern.replace(/\/?\*$/, "").replace(/^\.\//, "");
239
+ scanDirForExports(packageName, fullTargetDir, subpathPrefix, aliases);
240
+ }
241
+
242
+ /**
243
+ * Recursively scan a directory for .js files and register them as aliases.
244
+ */
245
+ function scanDirForExports(
246
+ packageName: string,
247
+ dir: string,
248
+ relativePath: string,
249
+ aliases: Record<string, string>,
250
+ ): void {
251
+ let entries: fs.Dirent[];
252
+ try {
253
+ entries = fs.readdirSync(dir, { withFileTypes: true });
254
+ } catch {
255
+ return;
256
+ }
257
+
258
+ for (const entry of entries) {
259
+ const entryRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
260
+
261
+ if (entry.isDirectory()) {
262
+ // Skip examples/test directories — extensions don't need them
263
+ if (entry.name === "examples" || entry.name === "__tests__" || entry.name === "test") continue;
264
+ scanDirForExports(packageName, path.join(dir, entry.name), entryRelative, aliases);
265
+ } else if (entry.name.endsWith(".js") && !entry.name.endsWith(".d.js")) {
266
+ const filePath = path.join(dir, entry.name);
267
+ const specifier = `${packageName}/${entryRelative}`;
268
+ // Only add if not already covered by an explicit export
269
+ if (!(specifier in aliases)) {
270
+ aliases[specifier] = filePath;
271
+ }
272
+ // Also add bare (no .js) variant
273
+ const bareSpecifier = specifier.replace(/\.js$/, "");
274
+ if (!(bareSpecifier in aliases)) {
275
+ aliases[bareSpecifier] = filePath;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
72
281
  function logExtensionTiming(extensionPath: string, ms: number, outcome: "loaded" | "failed"): void {
73
282
  if (!EXTENSION_TIMING_ENABLED) return;
74
283
  console.error(`[startup] extension ${outcome}: ${extensionPath} (${ms}ms)`);
@@ -100,7 +309,19 @@ function getAliases(): Record<string, string> {
100
309
  return fileURLToPath(import.meta.resolve(specifier));
101
310
  };
102
311
 
312
+ // Auto-discover subpath exports from bundled npm packages.
313
+ // This ensures extensions can import any standard subpath (e.g. @modelcontextprotocol/sdk/server)
314
+ // without hitting jiti's CJS double-resolve bug.
315
+ const autoDiscovered: Record<string, string> = {};
316
+ for (const packageName of BUNDLED_PACKAGES_WITH_EXPORTS) {
317
+ const subpathAliases = resolveSubpathExports(packageName);
318
+ Object.assign(autoDiscovered, subpathAliases);
319
+ }
320
+
103
321
  _aliases = {
322
+ // Auto-discovered subpath exports (lowest priority — overridden by manual entries below)
323
+ ...autoDiscovered,
324
+ // Manual entries for workspace packages and packages needing special resolution
104
325
  "@gsd/pi-coding-agent": packageIndex,
105
326
  "@gsd/pi-agent-core": resolveWorkspaceOrImport("agent/dist/index.js", "@gsd/pi-agent-core"),
106
327
  "@gsd/pi-tui": resolveWorkspaceOrImport("tui/dist/index.js", "@gsd/pi-tui"),
@@ -108,11 +329,6 @@ function getAliases(): Record<string, string> {
108
329
  "@gsd/pi-ai/oauth": resolveWorkspaceOrImport("ai/dist/oauth.js", "@gsd/pi-ai/oauth"),
109
330
  "@sinclair/typebox": typeboxRoot,
110
331
  "yaml": yamlRoot,
111
- "@modelcontextprotocol/sdk/client": require.resolve("@modelcontextprotocol/sdk/client"),
112
- "@modelcontextprotocol/sdk/client/stdio": require.resolve("@modelcontextprotocol/sdk/client/stdio.js"),
113
- "@modelcontextprotocol/sdk/client/stdio.js": require.resolve("@modelcontextprotocol/sdk/client/stdio.js"),
114
- "@modelcontextprotocol/sdk/client/streamableHttp": require.resolve("@modelcontextprotocol/sdk/client/streamableHttp.js"),
115
- "@modelcontextprotocol/sdk/client/streamableHttp.js": require.resolve("@modelcontextprotocol/sdk/client/streamableHttp.js"),
116
332
  // Aliases for external PI ecosystem packages that import from the original scope
117
333
  "@mariozechner/pi-coding-agent": packageIndex,
118
334
  "@mariozechner/pi-agent-core": resolveWorkspaceOrImport("agent/dist/index.js", "@gsd/pi-agent-core"),
@@ -81,6 +81,12 @@ export interface LoadSkillsResult {
81
81
  diagnostics: ResourceDiagnostic[];
82
82
  }
83
83
 
84
+ let loadedSkills: Skill[] = [];
85
+
86
+ export function getLoadedSkills(): Skill[] {
87
+ return [...loadedSkills];
88
+ }
89
+
84
90
  /**
85
91
  * Validate skill name per Agent Skills spec.
86
92
  * Returns array of validation error messages (empty if valid).
@@ -449,8 +455,10 @@ export function loadSkills(options: LoadSkillsOptions = {}): LoadSkillsResult {
449
455
  }
450
456
  }
451
457
 
458
+ loadedSkills = Array.from(skillMap.values());
459
+
452
460
  return {
453
- skills: Array.from(skillMap.values()),
461
+ skills: [...loadedSkills],
454
462
  diagnostics: [...allDiagnostics, ...collisionDiagnostics],
455
463
  };
456
464
  }
@@ -213,6 +213,7 @@ export {
213
213
  // Skills
214
214
  export {
215
215
  formatSkillsForPrompt,
216
+ getLoadedSkills,
216
217
  type LoadSkillsFromDirOptions,
217
218
  type LoadSkillsResult,
218
219
  loadSkills,
@@ -33,6 +33,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
33
33
  codegen,
34
34
  actionCache,
35
35
  injectionDetection,
36
+ verify,
36
37
  ] = await Promise.all([
37
38
  importExtensionModule<typeof import("./lifecycle.js")>(import.meta.url, "./lifecycle.js"),
38
39
  importExtensionModule<typeof import("./capture.js")>(import.meta.url, "./capture.js"),
@@ -60,6 +61,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
60
61
  importExtensionModule<typeof import("./tools/codegen.js")>(import.meta.url, "./tools/codegen.js"),
61
62
  importExtensionModule<typeof import("./tools/action-cache.js")>(import.meta.url, "./tools/action-cache.js"),
62
63
  importExtensionModule<typeof import("./tools/injection-detect.js")>(import.meta.url, "./tools/injection-detect.js"),
64
+ importExtensionModule<typeof import("./tools/verify.js")>(import.meta.url, "./tools/verify.js"),
63
65
  ]);
64
66
 
65
67
  const deps = {
@@ -132,6 +134,7 @@ async function registerBrowserTools(pi: ExtensionAPI): Promise<void> {
132
134
  codegen.registerCodegenTools(pi, deps);
133
135
  actionCache.registerActionCacheTools(pi, deps);
134
136
  injectionDetection.registerInjectionDetectionTools(pi, deps);
137
+ verify.registerVerifyTools(pi, deps);
135
138
  })().catch((error) => {
136
139
  registrationPromise = null;
137
140
  throw error;
@@ -0,0 +1,117 @@
1
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
2
+ import { Type } from "@sinclair/typebox";
3
+ import type { ToolDeps } from "../state.js";
4
+
5
+ export function registerVerifyTools(pi: ExtensionAPI, deps: ToolDeps): void {
6
+ pi.registerTool({
7
+ name: "browser_verify",
8
+ label: "Browser Verify",
9
+ description:
10
+ "Run a structured browser verification flow: navigate to a URL, run checks (element visibility, text content), capture screenshots as evidence, and return structured pass/fail results.",
11
+ promptGuidelines: [
12
+ "Use browser_verify for UAT verification flows that need structured evidence.",
13
+ "Each check produces a pass/fail result with captured evidence.",
14
+ "Prefer this over manual navigation + assertion sequences for verification tasks.",
15
+ ],
16
+ parameters: Type.Object({
17
+ url: Type.String({ description: "URL to navigate to" }),
18
+ checks: Type.Array(
19
+ Type.Object({
20
+ description: Type.String({ description: "What this check verifies" }),
21
+ selector: Type.Optional(Type.String({ description: "CSS selector to check" })),
22
+ expectedText: Type.Optional(Type.String({ description: "Expected text content" })),
23
+ expectedVisible: Type.Optional(Type.Boolean({ description: "Whether element should be visible" })),
24
+ screenshot: Type.Optional(Type.Boolean({ description: "Capture screenshot as evidence" })),
25
+ }),
26
+ { description: "Verification checks to run" },
27
+ ),
28
+ timeout: Type.Optional(Type.Number({ description: "Navigation timeout in ms", default: 10000 })),
29
+ }),
30
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
31
+ const startTime = Date.now();
32
+ const { page } = await deps.ensureBrowser();
33
+ const timeout = params.timeout ?? 10000;
34
+
35
+ try {
36
+ await page.goto(params.url, { waitUntil: "domcontentloaded", timeout });
37
+ } catch (navErr) {
38
+ const msg = navErr instanceof Error ? navErr.message : String(navErr);
39
+ return {
40
+ content: [{ type: "text" as const, text: `Navigation failed: ${msg}` }],
41
+ details: {
42
+ url: params.url,
43
+ passed: false,
44
+ checks: params.checks.map((c) => ({ description: c.description, passed: false, error: msg })),
45
+ duration: Date.now() - startTime,
46
+ },
47
+ };
48
+ }
49
+
50
+ const results: Array<{
51
+ description: string;
52
+ passed: boolean;
53
+ actual?: string;
54
+ evidence?: string;
55
+ error?: string;
56
+ }> = [];
57
+
58
+ for (const check of params.checks) {
59
+ try {
60
+ let passed = true;
61
+ let actual: string | undefined;
62
+ let evidence: string | undefined;
63
+
64
+ if (check.selector) {
65
+ const element = await page.$(check.selector);
66
+
67
+ if (check.expectedVisible !== undefined) {
68
+ const isVisible = element ? await element.isVisible() : false;
69
+ passed = isVisible === check.expectedVisible;
70
+ actual = `visible=${isVisible}`;
71
+ }
72
+
73
+ if (check.expectedText !== undefined && element) {
74
+ const text = await element.textContent();
75
+ passed = passed && (text?.includes(check.expectedText) ?? false);
76
+ actual = `text="${text?.slice(0, 200)}"`;
77
+ }
78
+
79
+ if (!element && (check.expectedVisible === true || check.expectedText)) {
80
+ passed = false;
81
+ actual = "element not found";
82
+ }
83
+ }
84
+
85
+ if (check.screenshot) {
86
+ try {
87
+ const buf = await page.screenshot({ type: "png" });
88
+ evidence = `screenshot captured (${buf.length} bytes)`;
89
+ } catch {
90
+ evidence = "screenshot failed";
91
+ }
92
+ }
93
+
94
+ results.push({ description: check.description, passed, actual, evidence });
95
+ } catch (checkErr) {
96
+ results.push({
97
+ description: check.description,
98
+ passed: false,
99
+ error: checkErr instanceof Error ? checkErr.message : String(checkErr),
100
+ });
101
+ }
102
+ }
103
+
104
+ const allPassed = results.every((r) => r.passed);
105
+ const summary = results.map((r) => `${r.passed ? "PASS" : "FAIL"}: ${r.description}${r.actual ? ` (${r.actual})` : ""}${r.error ? ` — ${r.error}` : ""}`).join("\n");
106
+ return {
107
+ content: [{ type: "text" as const, text: `Verification ${allPassed ? "PASSED" : "FAILED"} (${results.filter(r => r.passed).length}/${results.length})\n\n${summary}` }],
108
+ details: {
109
+ url: params.url,
110
+ passed: allPassed,
111
+ checks: results,
112
+ duration: Date.now() - startTime,
113
+ },
114
+ };
115
+ },
116
+ });
117
+ }
@@ -0,0 +1,31 @@
1
+ // GSD Extension — Environment variable utilities
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+ //
4
+ // Pure utility for checking existing env keys in .env files and process.env.
5
+ // Extracted from get-secrets-from-user.ts to avoid pulling in @gsd/pi-tui
6
+ // when only env-checking is needed (e.g. from files.ts during report generation).
7
+
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ /**
11
+ * Check which keys already exist in a .env file or process.env.
12
+ * Returns the subset of `keys` that are already set.
13
+ */
14
+ export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
15
+ let fileContent = "";
16
+ try {
17
+ fileContent = await readFile(envFilePath, "utf8");
18
+ } catch {
19
+ // ENOENT or other read error — proceed with empty content
20
+ }
21
+
22
+ const existing: string[] = [];
23
+ for (const key of keys) {
24
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ const regex = new RegExp(`^${escaped}\\s*=`, "m");
26
+ if (regex.test(fileContent) || key in process.env) {
27
+ existing.push(key);
28
+ }
29
+ }
30
+ return existing;
31
+ }
@@ -67,30 +67,11 @@ async function writeEnvKey(filePath: string, key: string, value: string): Promis
67
67
 
68
68
  // ─── Exported utilities ───────────────────────────────────────────────────────
69
69
 
70
- /**
71
- * Check which keys already exist in the .env file or process.env.
72
- * Returns the subset of `keys` that are already set.
73
- * Handles ENOENT gracefully (still checks process.env).
74
- * Empty-string values count as existing.
75
- */
76
- export async function checkExistingEnvKeys(keys: string[], envFilePath: string): Promise<string[]> {
77
- let fileContent = "";
78
- try {
79
- fileContent = await readFile(envFilePath, "utf8");
80
- } catch {
81
- // ENOENT or other read error — proceed with empty content
82
- }
83
-
84
- const existing: string[] = [];
85
- for (const key of keys) {
86
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
87
- const regex = new RegExp(`^${escaped}\\s*=`, "m");
88
- if (regex.test(fileContent) || key in process.env) {
89
- existing.push(key);
90
- }
91
- }
92
- return existing;
93
- }
70
+ // Re-export from env-utils.ts so existing consumers still work.
71
+ // The implementation lives in env-utils.ts to avoid pulling @gsd/pi-tui
72
+ // into modules that only need env-checking (e.g. files.ts during reports).
73
+ import { checkExistingEnvKeys } from "./env-utils.js";
74
+ export { checkExistingEnvKeys };
94
75
 
95
76
  /**
96
77
  * Detect the write destination based on project files in basePath.