@codewalla_india/openspec 1.3.0 → 1.3.2

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 (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +272 -230
  3. package/bin/openspec.js +5 -5
  4. package/dist/cli/index.js +99 -62
  5. package/dist/commands/change.d.ts +0 -2
  6. package/dist/commands/change.js +107 -86
  7. package/dist/commands/config.js +14 -16
  8. package/dist/commands/doctor.js +9 -4
  9. package/dist/commands/feedback.js +79 -30
  10. package/dist/commands/modify.d.ts +26 -0
  11. package/dist/commands/modify.js +147 -0
  12. package/dist/commands/schema.js +170 -91
  13. package/dist/commands/show.js +5 -2
  14. package/dist/commands/spec.js +49 -29
  15. package/dist/commands/store.js +15 -21
  16. package/dist/commands/validate.d.ts +8 -0
  17. package/dist/commands/validate.js +43 -20
  18. package/dist/commands/workflow/index.d.ts +2 -2
  19. package/dist/commands/workflow/index.js +1 -1
  20. package/dist/commands/workflow/instructions.d.ts +6 -26
  21. package/dist/commands/workflow/instructions.js +183 -376
  22. package/dist/commands/workflow/new-change.d.ts +0 -4
  23. package/dist/commands/workflow/new-change.js +10 -28
  24. package/dist/commands/workflow/shared.d.ts +34 -19
  25. package/dist/commands/workflow/shared.js +34 -4
  26. package/dist/commands/workflow/status.js +10 -32
  27. package/dist/commands/workflow/templates.js +15 -5
  28. package/dist/commands/workset.d.ts +2 -2
  29. package/dist/commands/workset.js +19 -22
  30. package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
  31. package/dist/comprehension-quiz/fingerprint.js +84 -0
  32. package/dist/comprehension-quiz/index.d.ts +42 -0
  33. package/dist/comprehension-quiz/index.js +47 -0
  34. package/dist/comprehension-quiz/pass-record.d.ts +44 -0
  35. package/dist/comprehension-quiz/pass-record.js +98 -0
  36. package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +53 -0
  38. package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
  39. package/dist/comprehension-quiz/providers/factory.js +174 -0
  40. package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
  41. package/dist/comprehension-quiz/providers/interface.js +63 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +34 -0
  43. package/dist/comprehension-quiz/providers/local.js +53 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +53 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
  47. package/dist/comprehension-quiz/providers/openai.js +53 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +69 -0
  49. package/dist/comprehension-quiz/question-generator.js +141 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +165 -0
  52. package/dist/comprehension-quiz/types.d.ts +115 -0
  53. package/dist/comprehension-quiz/types.js +11 -0
  54. package/dist/core/archive.d.ts +14 -1
  55. package/dist/core/archive.js +1350 -190
  56. package/dist/core/artifact-graph/graph.d.ts +28 -9
  57. package/dist/core/artifact-graph/graph.js +94 -43
  58. package/dist/core/artifact-graph/index.d.ts +1 -1
  59. package/dist/core/artifact-graph/index.js +1 -1
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
  61. package/dist/core/artifact-graph/instruction-loader.js +70 -12
  62. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  63. package/dist/core/artifact-graph/outputs.js +67 -5
  64. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  65. package/dist/core/artifact-graph/resolver.js +69 -15
  66. package/dist/core/artifact-graph/types.js +21 -3
  67. package/dist/core/available-tools.d.ts +2 -2
  68. package/dist/core/available-tools.js +14 -3
  69. package/dist/core/change-metadata/schema.d.ts +2 -0
  70. package/dist/core/change-metadata/schema.js +13 -0
  71. package/dist/core/change-status-policy.d.ts +10 -1
  72. package/dist/core/change-status-policy.js +30 -2
  73. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  74. package/dist/core/command-generation/adapters/amazon-q.js +11 -5
  75. package/dist/core/command-generation/adapters/antigravity.js +6 -5
  76. package/dist/core/command-generation/adapters/auggie.js +7 -6
  77. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  78. package/dist/core/command-generation/adapters/bob.js +11 -10
  79. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  80. package/dist/core/command-generation/adapters/claude.js +12 -17
  81. package/dist/core/command-generation/adapters/cline.js +5 -5
  82. package/dist/core/command-generation/adapters/codebuddy.js +8 -7
  83. package/dist/core/command-generation/adapters/continue.js +8 -7
  84. package/dist/core/command-generation/adapters/costrict.js +7 -6
  85. package/dist/core/command-generation/adapters/crush.js +9 -9
  86. package/dist/core/command-generation/adapters/cursor.js +8 -8
  87. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  88. package/dist/core/command-generation/adapters/devin.js +36 -0
  89. package/dist/core/command-generation/adapters/factory.js +7 -6
  90. package/dist/core/command-generation/adapters/gemini.js +40 -5
  91. package/dist/core/command-generation/adapters/github-copilot.js +6 -5
  92. package/dist/core/command-generation/adapters/iflow.js +9 -8
  93. package/dist/core/command-generation/adapters/index.d.ts +4 -2
  94. package/dist/core/command-generation/adapters/index.js +4 -2
  95. package/dist/core/command-generation/adapters/junie.js +6 -5
  96. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  97. package/dist/core/command-generation/adapters/kiro.js +6 -5
  98. package/dist/core/command-generation/adapters/lingma.js +9 -9
  99. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  101. package/dist/core/command-generation/adapters/opencode.js +6 -8
  102. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  103. package/dist/core/command-generation/adapters/pi.js +7 -10
  104. package/dist/core/command-generation/adapters/qoder.js +9 -9
  105. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  106. package/dist/core/command-generation/adapters/qwen.js +14 -9
  107. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  108. package/dist/core/command-generation/adapters/roocode.js +9 -9
  109. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  110. package/dist/core/command-generation/adapters/trae.js +28 -0
  111. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  112. package/dist/core/command-generation/adapters/zcode.js +33 -0
  113. package/dist/core/command-generation/generator.d.ts +8 -0
  114. package/dist/core/command-generation/generator.js +15 -1
  115. package/dist/core/command-generation/index.d.ts +1 -1
  116. package/dist/core/command-generation/index.js +1 -1
  117. package/dist/core/command-generation/invocation.d.ts +73 -0
  118. package/dist/core/command-generation/invocation.js +77 -0
  119. package/dist/core/command-generation/registry.js +8 -4
  120. package/dist/core/command-generation/types.d.ts +9 -2
  121. package/dist/core/command-generation/yaml.d.ts +20 -6
  122. package/dist/core/command-generation/yaml.js +31 -21
  123. package/dist/core/command-surface.d.ts +15 -0
  124. package/dist/core/command-surface.js +33 -0
  125. package/dist/core/completions/command-registry.js +16 -61
  126. package/dist/core/completions/generators/bash-generator.js +41 -41
  127. package/dist/core/completions/generators/fish-generator.js +7 -7
  128. package/dist/core/completions/generators/powershell-generator.js +44 -37
  129. package/dist/core/completions/generators/zsh-generator.js +33 -33
  130. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  131. package/dist/core/completions/installers/zsh-installer.js +20 -4
  132. package/dist/core/completions/templates/bash-templates.js +24 -24
  133. package/dist/core/completions/templates/fish-templates.js +38 -38
  134. package/dist/core/completions/templates/powershell-templates.js +28 -28
  135. package/dist/core/completions/templates/zsh-templates.js +39 -39
  136. package/dist/core/config-prompts.d.ts +1 -1
  137. package/dist/core/config-prompts.js +14 -1
  138. package/dist/core/config-schema.d.ts +10 -1
  139. package/dist/core/config-schema.js +67 -1
  140. package/dist/core/config.d.ts +15 -0
  141. package/dist/core/config.js +46 -5
  142. package/dist/core/file-state.js +56 -24
  143. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  144. package/dist/core/github-copilot/cloud-agent.js +497 -0
  145. package/dist/core/global-config.d.ts +16 -0
  146. package/dist/core/init.d.ts +58 -0
  147. package/dist/core/init.js +474 -73
  148. package/dist/core/legacy-cleanup.d.ts +59 -0
  149. package/dist/core/legacy-cleanup.js +253 -24
  150. package/dist/core/list.js +23 -16
  151. package/dist/core/migration.d.ts +73 -1
  152. package/dist/core/migration.js +360 -11
  153. package/dist/core/modify/conflict-detector.d.ts +26 -0
  154. package/dist/core/modify/conflict-detector.js +62 -0
  155. package/dist/core/modify/dependency-propagator.d.ts +38 -0
  156. package/dist/core/modify/dependency-propagator.js +103 -0
  157. package/dist/core/modify/history-tracker.d.ts +34 -0
  158. package/dist/core/modify/history-tracker.js +114 -0
  159. package/dist/core/modify/index.d.ts +7 -0
  160. package/dist/core/modify/index.js +7 -0
  161. package/dist/core/onboarding-commands.d.ts +30 -0
  162. package/dist/core/onboarding-commands.js +38 -0
  163. package/dist/core/openspec-root.js +18 -15
  164. package/dist/core/parsers/change-parser.d.ts +17 -2
  165. package/dist/core/parsers/change-parser.js +35 -21
  166. package/dist/core/parsers/code-fence.d.ts +15 -0
  167. package/dist/core/parsers/code-fence.js +49 -0
  168. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  169. package/dist/core/parsers/markdown-parser.js +7 -60
  170. package/dist/core/parsers/requirement-blocks.d.ts +29 -0
  171. package/dist/core/parsers/requirement-blocks.js +150 -31
  172. package/dist/core/parsers/requirement-text.d.ts +48 -0
  173. package/dist/core/parsers/requirement-text.js +98 -0
  174. package/dist/core/parsers/spec-structure.d.ts +1 -1
  175. package/dist/core/parsers/spec-structure.js +20 -30
  176. package/dist/core/profile-sync-drift.d.ts +0 -8
  177. package/dist/core/profile-sync-drift.js +49 -50
  178. package/dist/core/profiles.d.ts +2 -2
  179. package/dist/core/profiles.js +2 -2
  180. package/dist/core/project-config.d.ts +27 -18
  181. package/dist/core/project-config.js +101 -34
  182. package/dist/core/references.js +42 -4
  183. package/dist/core/relationship-health.d.ts +8 -0
  184. package/dist/core/relationship-health.js +13 -0
  185. package/dist/core/root-selection.d.ts +9 -6
  186. package/dist/core/root-selection.js +42 -5
  187. package/dist/core/schemas/base.schema.js +7 -2
  188. package/dist/core/shared/allowed-tools.d.ts +12 -0
  189. package/dist/core/shared/allowed-tools.js +12 -0
  190. package/dist/core/shared/index.d.ts +1 -0
  191. package/dist/core/shared/index.js +1 -0
  192. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  193. package/dist/core/shared/skill-content-equivalence.js +41 -0
  194. package/dist/core/shared/skill-generation.js +17 -13
  195. package/dist/core/shared/skill-paths.d.ts +13 -0
  196. package/dist/core/shared/skill-paths.js +23 -0
  197. package/dist/core/shared/tool-detection.d.ts +27 -7
  198. package/dist/core/shared/tool-detection.js +167 -37
  199. package/dist/core/shared-skill-target.d.ts +16 -0
  200. package/dist/core/shared-skill-target.js +156 -0
  201. package/dist/core/specs-apply.d.ts +80 -33
  202. package/dist/core/specs-apply.js +667 -145
  203. package/dist/core/store/git.d.ts +13 -0
  204. package/dist/core/store/git.js +24 -0
  205. package/dist/core/store/operations.js +20 -0
  206. package/dist/core/templates/skill-templates.d.ts +3 -2
  207. package/dist/core/templates/skill-templates.js +3 -2
  208. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  209. package/dist/core/templates/workflows/apply-change.js +186 -323
  210. package/dist/core/templates/workflows/archive-change.js +387 -260
  211. package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
  212. package/dist/core/templates/workflows/continue-change.js +210 -219
  213. package/dist/core/templates/workflows/explore.js +479 -443
  214. package/dist/core/templates/workflows/feedback.js +97 -97
  215. package/dist/core/templates/workflows/ff-change.js +200 -191
  216. package/dist/core/templates/workflows/modify-change.d.ts +4 -1
  217. package/dist/core/templates/workflows/modify-change.js +237 -115
  218. package/dist/core/templates/workflows/new-change.js +127 -134
  219. package/dist/core/templates/workflows/onboard.js +548 -542
  220. package/dist/core/templates/workflows/propose.js +273 -217
  221. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  222. package/dist/core/templates/workflows/store-selection.js +1 -1
  223. package/dist/core/templates/workflows/sync-specs.js +500 -273
  224. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  225. package/dist/core/templates/workflows/update-change.js +177 -0
  226. package/dist/core/templates/workflows/verify-change.js +324 -326
  227. package/dist/core/update.d.ts +40 -5
  228. package/dist/core/update.js +411 -96
  229. package/dist/core/validation/constants.d.ts +4 -1
  230. package/dist/core/validation/constants.js +4 -1
  231. package/dist/core/validation/plan-validator.d.ts +8 -0
  232. package/dist/core/validation/plan-validator.js +45 -0
  233. package/dist/core/validation/task-numbering.d.ts +16 -0
  234. package/dist/core/validation/task-numbering.js +57 -0
  235. package/dist/core/validation/validator.d.ts +27 -3
  236. package/dist/core/validation/validator.js +337 -48
  237. package/dist/core/version-check.d.ts +152 -0
  238. package/dist/core/version-check.js +731 -0
  239. package/dist/core/view.js +13 -18
  240. package/dist/prompts/searchable-multi-select.js +1 -1
  241. package/dist/telemetry/caller-detection.d.ts +27 -0
  242. package/dist/telemetry/caller-detection.js +85 -0
  243. package/dist/telemetry/caller-detection.test.d.ts +5 -0
  244. package/dist/telemetry/config.d.ts +48 -2
  245. package/dist/telemetry/config.js +113 -11
  246. package/dist/telemetry/config.test.d.ts +5 -0
  247. package/dist/telemetry/deduplication.d.ts +35 -0
  248. package/dist/telemetry/deduplication.js +82 -0
  249. package/dist/telemetry/deduplication.test.d.ts +5 -0
  250. package/dist/telemetry/identity.d.ts +62 -22
  251. package/dist/telemetry/identity.js +147 -102
  252. package/dist/telemetry/index.d.ts +126 -15
  253. package/dist/telemetry/index.js +569 -37
  254. package/dist/telemetry/sanitization.d.ts +40 -0
  255. package/dist/telemetry/sanitization.js +114 -0
  256. package/dist/telemetry/sanitization.test.d.ts +5 -0
  257. package/dist/telemetry/throttling.d.ts +20 -0
  258. package/dist/telemetry/throttling.js +70 -0
  259. package/dist/ui/welcome-screen.d.ts +9 -1
  260. package/dist/ui/welcome-screen.js +91 -38
  261. package/dist/utils/change-metadata.d.ts +43 -0
  262. package/dist/utils/change-metadata.js +123 -7
  263. package/dist/utils/change-utils.d.ts +10 -4
  264. package/dist/utils/change-utils.js +20 -13
  265. package/dist/utils/ci.d.ts +12 -0
  266. package/dist/utils/ci.js +16 -0
  267. package/dist/utils/command-references.d.ts +92 -6
  268. package/dist/utils/command-references.js +180 -7
  269. package/dist/utils/date.d.ts +8 -0
  270. package/dist/utils/date.js +13 -0
  271. package/dist/utils/file-system.d.ts +10 -0
  272. package/dist/utils/file-system.js +70 -0
  273. package/dist/utils/index.d.ts +1 -1
  274. package/dist/utils/index.js +1 -1
  275. package/dist/utils/interactive.d.ts +23 -0
  276. package/dist/utils/interactive.js +32 -0
  277. package/dist/utils/item-discovery.d.ts +19 -0
  278. package/dist/utils/item-discovery.js +30 -48
  279. package/dist/utils/shell-detection.d.ts +1 -1
  280. package/dist/utils/shell-detection.js +64 -11
  281. package/dist/utils/spec-discovery.d.ts +40 -0
  282. package/dist/utils/spec-discovery.js +115 -0
  283. package/dist/utils/task-progress.d.ts +29 -1
  284. package/dist/utils/task-progress.js +109 -15
  285. package/package.json +96 -84
  286. package/schemas/spec-driven/schema.yaml +252 -184
  287. package/schemas/spec-driven/templates/design.md +19 -19
  288. package/schemas/spec-driven/templates/plan-template.md +91 -0
  289. package/schemas/spec-driven/templates/proposal.md +29 -24
  290. package/schemas/spec-driven/templates/spec.md +11 -8
  291. package/schemas/spec-driven/templates/tasks.md +9 -9
  292. package/scripts/postinstall.js +83 -83
  293. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  294. package/dist/core/command-generation/adapters/codex.js +0 -39
  295. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  296. package/dist/core/command-generation/adapters/windsurf.js +0 -38
  297. package/dist/core/comprehension/config.d.ts +0 -20
  298. package/dist/core/comprehension/config.js +0 -23
  299. package/dist/core/comprehension/fingerprint.d.ts +0 -14
  300. package/dist/core/comprehension/fingerprint.js +0 -33
  301. package/dist/core/comprehension/index.d.ts +0 -57
  302. package/dist/core/comprehension/index.js +0 -87
  303. package/dist/core/comprehension/pass-record.d.ts +0 -29
  304. package/dist/core/comprehension/pass-record.js +0 -64
  305. package/dist/core/comprehension/stats.d.ts +0 -35
  306. package/dist/core/comprehension/stats.js +0 -133
  307. package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
  308. package/dist/core/templates/workflows/comprehension-guidance.js +0 -71
  309. package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
  310. package/dist/core/templates/workflows/mcp-guidance.js +0 -131
  311. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
  312. package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
  313. package/dist/telemetry/caller.d.ts +0 -5
  314. package/dist/telemetry/caller.js +0 -29
  315. package/dist/telemetry/client.d.ts +0 -27
  316. package/dist/telemetry/client.js +0 -127
  317. package/dist/telemetry/command-context.d.ts +0 -13
  318. package/dist/telemetry/command-context.js +0 -59
  319. package/dist/telemetry/comprehension.d.ts +0 -44
  320. package/dist/telemetry/comprehension.js +0 -105
  321. package/dist/telemetry/content.d.ts +0 -10
  322. package/dist/telemetry/content.js +0 -56
  323. package/dist/telemetry/git-stats.d.ts +0 -12
  324. package/dist/telemetry/git-stats.js +0 -69
  325. package/dist/telemetry/identify-cache.d.ts +0 -7
  326. package/dist/telemetry/identify-cache.js +0 -47
  327. package/dist/telemetry/input.d.ts +0 -17
  328. package/dist/telemetry/input.js +0 -68
  329. package/dist/telemetry/marker.d.ts +0 -37
  330. package/dist/telemetry/marker.js +0 -67
  331. package/dist/telemetry/workflow.d.ts +0 -85
  332. package/dist/telemetry/workflow.js +0 -318
  333. package/schemas/spec-driven/templates/plan.md +0 -19
@@ -1,45 +1,577 @@
1
1
  /**
2
- * Codewalla PostHog telemetry — mandatory when identity is available.
3
- */
4
- import { resolveTelemetryUserId } from './identity.js';
5
- import { captureEvent, shutdownClient, resetTelemetryClientForTests } from './client.js';
6
- import { sanitizeErrorForTelemetry } from './content.js';
7
- export async function canSendTelemetry() {
8
- const userId = await resolveTelemetryUserId({ prompt: false });
9
- return userId !== null;
10
- }
11
- export { TelemetryIdentityRequiredError, buildIdentityRequiredMessage, resolveTelemetryUserId, requireTelemetryIdentity, setupTelemetryIdentity, promptAndStoreTelemetryIdentity, getIdentityFilePath, validateUserId, ensureTelemetryIdentity, } from './identity.js';
12
- export { DEFAULT_POSTHOG_KEY, DEFAULT_POSTHOG_HOST, safeTelemetryFetch } from './client.js';
13
- export { sanitizeWorkflowInput, sanitizeTelemetryContent, readSanitizedFile, readWorkflowInputFile, normalizeEditor, resolveWorkflowInputAsync, VALID_EDITORS, MAX_ARTIFACT_BODY_LENGTH, } from './input.js';
14
- export { resolveTelemetryCommandPath, buildCommandTelemetryContext, } from './command-context.js';
15
- export { resolveCaller } from './caller.js';
16
- export async function trackCommand(commandName, version, context) {
17
- await captureEvent('command_executed', {
18
- command: commandName,
19
- version,
20
- ...(context?.change_name ? { change_name: context.change_name } : {}),
21
- ...(context?.schema ? { schema: context.schema } : {}),
22
- ...(context?.command_category ? { command_category: context.command_category } : {}),
23
- });
2
+ * Telemetry module for identity-based usage analytics.
3
+ *
4
+ * Privacy-first design:
5
+ * - Only tracks command name and version
6
+ * - No arguments, file paths, or content
7
+ * - Identity-based tracking with email or username
8
+ * - Identity stored securely in separate file with 0600 permissions
9
+ * - Identity is never logged or exposed in output
10
+ *
11
+ * Events are sent with a plain fetch to PostHog's stable public `/batch/`
12
+ * endpoint the same one posthog-node used — instead of through the SDK.
13
+ * The SDK's only remaining job here was the wire format: every reliability
14
+ * knob was already forced to "send one event immediately, time-bounded,
15
+ * never retry, never throw". Carrying `posthog-node` for that shipped its
16
+ * fast-moving transitive tree (`@posthog/core`, `@posthog/types`, multiple
17
+ * releases per day) to every downstream consumer, where supply-chain age
18
+ * policies such as pnpm's `minimumReleaseAge` rejected the freshly published
19
+ * versions and broke installs (#1390).
20
+ */
21
+ import { getGlobalConfig } from '../core/global-config.js';
22
+ import { getTelemetryConfig, updateTelemetryConfig, getPostHogApiKey, getPostHogHost } from './config.js';
23
+ import { detectCaller } from './caller-detection.js';
24
+ import { readIdentity } from './identity.js';
25
+ import { shouldThrottleIdentify, recordIdentifyCall } from './throttling.js';
26
+ const TELEMETRY_REQUEST_TIMEOUT_MS = 1000;
27
+ /**
28
+ * Requests started by trackCommand and not yet settled, so shutdown can
29
+ * flush them before the process exits. Each request is individually
30
+ * time-bounded, so awaiting them cannot stall exit for more than the
31
+ * request timeout.
32
+ */
33
+ const pendingEvents = new Set();
34
+ async function safeTelemetryFetch(url, options) {
35
+ try {
36
+ const response = await fetch(url, options);
37
+ // Telemetry never reads the body, but undici keeps the connection
38
+ // occupied until the body is consumed or canceled — dispose of it on
39
+ // every path so no socket outlives shutdown().
40
+ if (response.body) {
41
+ await response.body.cancel();
42
+ }
43
+ if (response.ok) {
44
+ return response;
45
+ }
46
+ }
47
+ catch {
48
+ // Silent failure - telemetry should never surface network noise
49
+ }
50
+ return new Response(null, { status: 204 });
24
51
  }
25
- export async function trackEvent(event, properties = {}) {
26
- await captureEvent(event, properties);
52
+ /**
53
+ * Check if telemetry is enabled.
54
+ *
55
+ * Identity-based telemetry is mandatory for all commands except init and update.
56
+ * Opt-out mechanisms have been removed.
57
+ *
58
+ * Kept synchronous so call sites need not become async. Reads config via
59
+ * sync getGlobalConfig() rather than async getTelemetryConfig().
60
+ */
61
+ export function isTelemetryEnabled() {
62
+ // Global config opt-out only
63
+ if (getGlobalConfig().telemetry?.enabled === false) {
64
+ return false;
65
+ }
66
+ return true;
67
+ }
68
+ /**
69
+ * Get the user identity for telemetry.
70
+ * Reads from identity file or environment variable.
71
+ */
72
+ export async function getIdentity() {
73
+ return await readIdentity();
27
74
  }
28
- export async function trackCommandFailed(command, error, errorCode) {
29
- const errorDetails = sanitizeErrorForTelemetry(error);
30
- await trackEvent('command_failed', {
31
- command,
32
- error_code: errorCode ?? (error instanceof Error ? error.name : 'unknown'),
33
- ...errorDetails,
75
+ /**
76
+ * Send $identify event to PostHog with throttling.
77
+ * Called once per user per 24 hours to update user profile.
78
+ */
79
+ async function sendIdentify(identity) {
80
+ // Check if we should throttle this $identify call
81
+ if (await shouldThrottleIdentify(identity)) {
82
+ return;
83
+ }
84
+ // Record that we're making an $identify call
85
+ await recordIdentifyCall(identity);
86
+ const body = JSON.stringify({
87
+ api_key: getPostHogApiKey(),
88
+ batch: [
89
+ {
90
+ type: 'identify',
91
+ distinct_id: identity,
92
+ properties: {
93
+ $ip: null, // Explicitly disable IP tracking
94
+ },
95
+ timestamp: new Date().toISOString(),
96
+ },
97
+ ],
34
98
  });
99
+ const request = safeTelemetryFetch(`${getPostHogHost()}/batch/`, {
100
+ method: 'POST',
101
+ headers: { 'content-type': 'application/json' },
102
+ body,
103
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS),
104
+ }).then(() => undefined);
105
+ pendingEvents.add(request);
106
+ void request.finally(() => pendingEvents.delete(request));
35
107
  }
36
- export async function shutdown() {
37
- await shutdownClient();
108
+ /**
109
+ * Send one capture event to PostHog's batch endpoint. Fire-and-forget:
110
+ * bounded by the request timeout, never throws, never retries.
111
+ */
112
+ function sendEvent(distinctId, event, properties) {
113
+ const body = JSON.stringify({
114
+ api_key: getPostHogApiKey(),
115
+ batch: [
116
+ {
117
+ type: 'capture',
118
+ event,
119
+ distinct_id: distinctId,
120
+ properties,
121
+ timestamp: new Date().toISOString(),
122
+ },
123
+ ],
124
+ });
125
+ const request = safeTelemetryFetch(`${getPostHogHost()}/batch/`, {
126
+ method: 'POST',
127
+ headers: { 'content-type': 'application/json' },
128
+ body,
129
+ signal: AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS),
130
+ }).then(() => undefined);
131
+ pendingEvents.add(request);
132
+ void request.finally(() => pendingEvents.delete(request));
133
+ }
134
+ /**
135
+ * Track a command execution.
136
+ *
137
+ * @param commandName - The command name (e.g., 'init', 'change:apply')
138
+ * @param version - The OpenSpec version
139
+ */
140
+ export async function trackCommand(commandName, version) {
141
+ if (!isTelemetryEnabled()) {
142
+ return;
143
+ }
144
+ try {
145
+ const identity = await getIdentity();
146
+ if (!identity) {
147
+ return; // Skip telemetry if no identity configured
148
+ }
149
+ const caller = detectCaller();
150
+ // Send $identify call with throttling
151
+ await sendIdentify(identity);
152
+ sendEvent(identity, 'command_executed', {
153
+ command: commandName,
154
+ version,
155
+ surface: 'cli',
156
+ caller,
157
+ $ip: null, // Explicitly disable IP tracking
158
+ });
159
+ }
160
+ catch {
161
+ // Silent failure - telemetry should never break CLI
162
+ }
163
+ }
164
+ /**
165
+ * Track a comprehension quiz attempt.
166
+ *
167
+ * @param changeName - The name of the change
168
+ * @param attemptNumber - The attempt number
169
+ */
170
+ export async function trackComprehensionAttempt(changeName, attemptNumber) {
171
+ if (!isTelemetryEnabled()) {
172
+ return;
173
+ }
174
+ try {
175
+ const identity = await getIdentity();
176
+ if (!identity) {
177
+ return; // Skip telemetry if no identity configured
178
+ }
179
+ const caller = detectCaller();
180
+ sendEvent(identity, 'comprehension_attempt', {
181
+ changeName,
182
+ attemptNumber,
183
+ caller,
184
+ $ip: null, // Explicitly disable IP tracking
185
+ });
186
+ }
187
+ catch {
188
+ // Silent failure - telemetry should never break CLI
189
+ }
190
+ }
191
+ /**
192
+ * Track a comprehension quiz completion.
193
+ *
194
+ * @param changeName - The name of the change
195
+ * @param scorePercent - The score percentage (0-100)
196
+ * @param result - The result ('pass' or 'fail')
197
+ * @param attemptNumber - The attempt number
198
+ */
199
+ export async function trackComprehensionCompletion(changeName, scorePercent, result, attemptNumber) {
200
+ if (!isTelemetryEnabled()) {
201
+ return;
202
+ }
203
+ try {
204
+ const identity = await getIdentity();
205
+ if (!identity) {
206
+ return; // Skip telemetry if no identity configured
207
+ }
208
+ const caller = detectCaller();
209
+ sendEvent(identity, 'comprehension_completion', {
210
+ changeName,
211
+ scorePercent,
212
+ result,
213
+ attemptNumber,
214
+ caller,
215
+ $ip: null, // Explicitly disable IP tracking
216
+ });
217
+ }
218
+ catch {
219
+ // Silent failure - telemetry should never break CLI
220
+ }
221
+ }
222
+ /**
223
+ * Track when apply becomes ready after quiz pass.
224
+ *
225
+ * @param changeName - The name of the the change
226
+ */
227
+ export async function trackApplyReady(changeName) {
228
+ if (!isTelemetryEnabled()) {
229
+ return;
230
+ }
231
+ try {
232
+ const identity = await getIdentity();
233
+ if (!identity) {
234
+ return; // Skip telemetry if no identity configured
235
+ }
236
+ const caller = detectCaller();
237
+ sendEvent(identity, 'next_milestone: apply_ready', {
238
+ changeName,
239
+ caller,
240
+ $ip: null, // Explicitly disable IP tracking
241
+ });
242
+ }
243
+ catch {
244
+ // Silent failure - telemetry should never break CLI
245
+ }
246
+ }
247
+ /**
248
+ * Track artifact modification request.
249
+ *
250
+ * @param changeName - The name of the change
251
+ * @param artifactId - The artifact being modified
252
+ */
253
+ export async function trackArtifactModifyRequested(changeName, artifactId) {
254
+ if (!isTelemetryEnabled()) {
255
+ return;
256
+ }
257
+ try {
258
+ const identity = await getIdentity();
259
+ if (!identity) {
260
+ return; // Skip telemetry if no identity configured
261
+ }
262
+ const caller = detectCaller();
263
+ sendEvent(identity, 'artifact_modify_requested', {
264
+ changeName,
265
+ artifactId,
266
+ caller,
267
+ $ip: null, // Explicitly disable IP tracking
268
+ });
269
+ }
270
+ catch {
271
+ // Silent failure - telemetry should never break CLI
272
+ }
273
+ }
274
+ /**
275
+ * Track artifact revision request (when downstream artifacts need regeneration).
276
+ *
277
+ * @param changeName - The name of the change
278
+ * @param artifactId - The artifact being regenerated
279
+ */
280
+ export async function trackArtifactRevisionRequested(changeName, artifactId) {
281
+ if (!isTelemetryEnabled()) {
282
+ return;
283
+ }
284
+ try {
285
+ const identity = await getIdentity();
286
+ if (!identity) {
287
+ return; // Skip telemetry if no identity configured
288
+ }
289
+ const caller = detectCaller();
290
+ sendEvent(identity, 'artifact_revision_requested', {
291
+ changeName,
292
+ artifactId,
293
+ caller,
294
+ $ip: null, // Explicitly disable IP tracking
295
+ });
296
+ }
297
+ catch {
298
+ // Silent failure - telemetry should never break CLI
299
+ }
38
300
  }
39
- /** @internal Test helper */
40
- export function resetTelemetryForTests() {
41
- resetTelemetryClientForTests();
301
+ /**
302
+ * Track artifact content change.
303
+ *
304
+ * @param changeName - The name of the change
305
+ * @param artifactId - The artifact whose content changed
306
+ */
307
+ export async function trackArtifactContentChanged(changeName, artifactId) {
308
+ if (!isTelemetryEnabled()) {
309
+ return;
310
+ }
311
+ try {
312
+ const identity = await getIdentity();
313
+ if (!identity) {
314
+ return; // Skip telemetry if no identity configured
315
+ }
316
+ const caller = detectCaller();
317
+ sendEvent(identity, 'artifact_content_changed', {
318
+ changeName,
319
+ artifactId,
320
+ caller,
321
+ $ip: null, // Explicitly disable IP tracking
322
+ });
323
+ }
324
+ catch {
325
+ // Silent failure - telemetry should never break CLI
326
+ }
327
+ }
328
+ /**
329
+ * Track workflow started event (when a new change is created).
330
+ *
331
+ * @param changeName - The name of the change
332
+ * @param workflowInput - Optional workflow input data
333
+ */
334
+ export async function trackWorkflowStarted(changeName, workflowInput) {
335
+ if (!isTelemetryEnabled()) {
336
+ return;
337
+ }
338
+ try {
339
+ const identity = await getIdentity();
340
+ if (!identity) {
341
+ return; // Skip telemetry if no identity configured
342
+ }
343
+ const caller = detectCaller();
344
+ sendEvent(identity, 'workflow_started', {
345
+ changeName,
346
+ caller,
347
+ ...(workflowInput && { workflowInput }),
348
+ $ip: null, // Explicitly disable IP tracking
349
+ });
350
+ }
351
+ catch {
352
+ // Silent failure - telemetry should never break CLI
353
+ }
354
+ }
355
+ /**
356
+ * Track change proposal ready event (when proposal artifact is created).
357
+ *
358
+ * @param changeName - The name of the change
359
+ */
360
+ export async function trackChangeProposalReady(changeName) {
361
+ if (!isTelemetryEnabled()) {
362
+ return;
363
+ }
364
+ try {
365
+ const identity = await getIdentity();
366
+ if (!identity) {
367
+ return; // Skip telemetry if no identity configured
368
+ }
369
+ const caller = detectCaller();
370
+ sendEvent(identity, 'change_proposal_ready', {
371
+ changeName,
372
+ caller,
373
+ $ip: null, // Explicitly disable IP tracking
374
+ });
375
+ }
376
+ catch {
377
+ // Silent failure - telemetry should never break CLI
378
+ }
379
+ }
380
+ /**
381
+ * Track change archived event (when a change is archived).
382
+ *
383
+ * @param changeName - The name of the change
384
+ */
385
+ export async function trackChangeArchived(changeName) {
386
+ if (!isTelemetryEnabled()) {
387
+ return;
388
+ }
389
+ try {
390
+ const identity = await getIdentity();
391
+ if (!identity) {
392
+ return; // Skip telemetry if no identity configured
393
+ }
394
+ const caller = detectCaller();
395
+ sendEvent(identity, 'change_archived', {
396
+ changeName,
397
+ caller,
398
+ $ip: null, // Explicitly disable IP tracking
399
+ });
400
+ }
401
+ catch {
402
+ // Silent failure - telemetry should never break CLI
403
+ }
404
+ }
405
+ /**
406
+ * Track store operation event.
407
+ *
408
+ * @param operation - The store operation (add, remove, list, register, unregister, doctor)
409
+ * @param storeId - Optional store ID
410
+ */
411
+ export async function trackStoreOperation(operation, storeId) {
412
+ if (!isTelemetryEnabled()) {
413
+ return;
414
+ }
415
+ try {
416
+ const identity = await getIdentity();
417
+ if (!identity) {
418
+ return; // Skip telemetry if no identity configured
419
+ }
420
+ const caller = detectCaller();
421
+ sendEvent(identity, 'store_operation', {
422
+ operation,
423
+ ...(storeId && { storeId }),
424
+ caller,
425
+ $ip: null, // Explicitly disable IP tracking
426
+ });
427
+ }
428
+ catch {
429
+ // Silent failure - telemetry should never break CLI
430
+ }
431
+ }
432
+ /**
433
+ * Track workset operation event.
434
+ *
435
+ * @param operation - The workset operation (create, list, open, remove)
436
+ * @param worksetName - Optional workset name
437
+ */
438
+ export async function trackWorksetOperation(operation, worksetName) {
439
+ if (!isTelemetryEnabled()) {
440
+ return;
441
+ }
442
+ try {
443
+ const identity = await getIdentity();
444
+ if (!identity) {
445
+ return; // Skip telemetry if no identity configured
446
+ }
447
+ const caller = detectCaller();
448
+ sendEvent(identity, 'workset_operation', {
449
+ operation,
450
+ ...(worksetName && { worksetName }),
451
+ caller,
452
+ $ip: null, // Explicitly disable IP tracking
453
+ });
454
+ }
455
+ catch {
456
+ // Silent failure - telemetry should never break CLI
457
+ }
458
+ }
459
+ /**
460
+ * Track validation operation event.
461
+ *
462
+ * @param changeName - The change name being validated
463
+ * @param result - Validation result (valid, invalid, warnings)
464
+ */
465
+ export async function trackValidationOperation(changeName, result) {
466
+ if (!isTelemetryEnabled()) {
467
+ return;
468
+ }
469
+ try {
470
+ const identity = await getIdentity();
471
+ if (!identity) {
472
+ return; // Skip telemetry if no identity configured
473
+ }
474
+ const caller = detectCaller();
475
+ sendEvent(identity, 'validation_operation', {
476
+ changeName,
477
+ result,
478
+ caller,
479
+ $ip: null, // Explicitly disable IP tracking
480
+ });
481
+ }
482
+ catch {
483
+ // Silent failure - telemetry should never break CLI
484
+ }
485
+ }
486
+ /**
487
+ * Track config change event.
488
+ *
489
+ * @param operation - The config operation (set, unset)
490
+ * @param key - The config key being changed
491
+ */
492
+ export async function trackConfigChange(operation, key) {
493
+ if (!isTelemetryEnabled()) {
494
+ return;
495
+ }
496
+ try {
497
+ const identity = await getIdentity();
498
+ if (!identity) {
499
+ return; // Skip telemetry if no identity configured
500
+ }
501
+ const caller = detectCaller();
502
+ sendEvent(identity, 'config_change', {
503
+ operation,
504
+ key,
505
+ caller,
506
+ $ip: null, // Explicitly disable IP tracking
507
+ });
508
+ }
509
+ catch {
510
+ // Silent failure - telemetry should never break CLI
511
+ }
512
+ }
513
+ /**
514
+ * Track feedback submission event.
515
+ *
516
+ * @param category - Feedback category
517
+ */
518
+ export async function trackFeedbackSubmission(category) {
519
+ if (!isTelemetryEnabled()) {
520
+ return;
521
+ }
522
+ try {
523
+ const identity = await getIdentity();
524
+ if (!identity) {
525
+ return; // Skip telemetry if no identity configured
526
+ }
527
+ const caller = detectCaller();
528
+ sendEvent(identity, 'feedback_submission', {
529
+ category,
530
+ caller,
531
+ $ip: null, // Explicitly disable IP tracking
532
+ });
533
+ }
534
+ catch {
535
+ // Silent failure - telemetry should never break CLI
536
+ }
537
+ }
538
+ /**
539
+ * Show first-run telemetry notice if not already seen.
540
+ */
541
+ export async function maybeShowTelemetryNotice() {
542
+ if (!isTelemetryEnabled()) {
543
+ return;
544
+ }
545
+ try {
546
+ const config = await getTelemetryConfig();
547
+ if (config.noticeSeen) {
548
+ return;
549
+ }
550
+ // Display notice
551
+ console.log('Note: OpenSpec requires identity for telemetry. Configure with: openspec init');
552
+ // Mark as seen
553
+ await updateTelemetryConfig({ noticeSeen: true });
554
+ }
555
+ catch {
556
+ // Silent failure - telemetry should never break CLI
557
+ }
558
+ }
559
+ /**
560
+ * Flush pending telemetry events.
561
+ * Call this before CLI exit.
562
+ */
563
+ export async function shutdown() {
564
+ if (pendingEvents.size === 0) {
565
+ return;
566
+ }
567
+ try {
568
+ await Promise.allSettled([...pendingEvents]);
569
+ }
570
+ catch {
571
+ // Silent failure - telemetry should never break CLI exit
572
+ }
573
+ finally {
574
+ pendingEvents.clear();
575
+ }
42
576
  }
43
- export { trackWorkflowStarted, maybeEmitProposalReady, maybeEmitApplyReady, trackArtifactInstructions, trackArtifactContentChanges, trackArtifactModifyRequested, trackChangeArchived, buildSpecDeltasFromUpdates, } from './workflow.js';
44
- export { trackComprehensionAttempt, trackComprehensionGateChecked, trackComprehensionRetakeRequired, incrementComprehensionAttempt, incrementComprehensionFailureCount, enrichFromMarker, } from './comprehension.js';
45
577
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Data sanitization module for telemetry.
3
+ *
4
+ * Sanitizes sensitive information from telemetry events:
5
+ * - Removes file paths from stack traces while preserving function names
6
+ * - Excludes file paths from event properties
7
+ * - Sanitizes artifact bodies for comprehension events
8
+ * - Ensures IP address exclusion consistency
9
+ */
10
+ /**
11
+ * Sanitize a stack trace by removing file paths while preserving function names and error types.
12
+ *
13
+ * @param stackTrace - The raw stack trace string
14
+ * @returns The sanitized stack trace
15
+ */
16
+ export declare function sanitizeStackTrace(stackTrace: string): string;
17
+ /**
18
+ * Sanitize an object by removing file paths from string properties.
19
+ *
20
+ * @param obj - The object to sanitize
21
+ * @returns The sanitized object
22
+ */
23
+ export declare function sanitizeObject(obj: Record<string, unknown>): Record<string, unknown>;
24
+ /**
25
+ * Sanitize artifact body content for comprehension events.
26
+ *
27
+ * Removes file paths and sensitive information while preserving code structure.
28
+ *
29
+ * @param content - The artifact body content
30
+ * @returns The sanitized artifact body
31
+ */
32
+ export declare function sanitizeArtifactBody(content: string): string;
33
+ /**
34
+ * Ensure IP address exclusion by setting $ip to null in event properties.
35
+ *
36
+ * @param properties - The event properties object
37
+ * @returns The properties with $ip set to null
38
+ */
39
+ export declare function ensureIpExclusion(properties: Record<string, unknown>): Record<string, unknown>;
40
+ //# sourceMappingURL=sanitization.d.ts.map