@codewalla_india/openspec 1.3.1 → 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 -191
  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 -224
  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 -73
  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,125 +1,170 @@
1
1
  /**
2
- * Codewalla telemetry identity — human-readable userId (email or username).
3
- * Stored globally at ~/.config/openspec/telemetry-identity.json (mode 0600).
2
+ * Identity management for telemetry.
3
+ *
4
+ * Handles identity collection, storage, validation, and retrieval.
5
+ * Identity is stored securely in a separate file with restricted permissions.
4
6
  */
5
- import { input } from '@inquirer/prompts';
6
7
  import { promises as fs } from 'fs';
7
8
  import path from 'path';
8
9
  import { getGlobalConfigDir } from '../core/global-config.js';
9
- export const IDENTITY_FILENAME = 'telemetry-identity.json';
10
- const USERNAME_REGEX = /^[a-zA-Z0-9._-]{2,64}$/;
11
- const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
12
- let cachedUserId = null;
13
- export class TelemetryIdentityRequiredError extends Error {
14
- code = 'telemetry_identity_required';
15
- constructor(message) {
16
- super(message ?? buildIdentityRequiredMessage());
17
- this.name = 'TelemetryIdentityRequiredError';
18
- }
19
- }
20
- export function buildIdentityRequiredMessage() {
21
- return (`Telemetry identity required. Run \`openspec init\` or \`openspec update\` interactively, ` +
22
- `or create ${getIdentityFilePath()}.`);
10
+ // Constants
11
+ export const IDENTITY_FILE_NAME = 'telemetry-identity.json';
12
+ /**
13
+ * Get the path to the identity file.
14
+ * Follows platform-specific config directory conventions.
15
+ *
16
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
17
+ * - Unix/macOS fallback: ~/.config/openspec/
18
+ * - Windows fallback: %APPDATA%/openspec/
19
+ */
20
+ export function getIdentityPath() {
21
+ const configDir = getGlobalConfigDir();
22
+ return path.join(configDir, IDENTITY_FILE_NAME);
23
23
  }
24
- export function validateUserId(value) {
25
- const trimmed = value.trim();
26
- if (!trimmed) {
27
- return 'Email or username is required';
28
- }
29
- if (EMAIL_REGEX.test(trimmed) || USERNAME_REGEX.test(trimmed)) {
30
- return true;
24
+ /**
25
+ * Read identity from file or environment variable.
26
+ * Environment variable takes precedence over file.
27
+ *
28
+ * @returns The identity string, or null if not configured
29
+ */
30
+ export async function readIdentity() {
31
+ // Check environment variable first (highest priority)
32
+ const envIdentity = process.env.OPENSPEC_TELEMETRY_USER;
33
+ if (envIdentity) {
34
+ return envIdentity.trim();
31
35
  }
32
- return 'Enter a valid email (user@example.com) or username (2–64 chars: letters, numbers, . _ -)';
33
- }
34
- export function getIdentityFilePath() {
35
- return path.join(getGlobalConfigDir(), IDENTITY_FILENAME);
36
- }
37
- export async function readStoredUserId() {
36
+ // Fall back to file
37
+ const identityPath = getIdentityPath();
38
38
  try {
39
- const content = await fs.readFile(getIdentityFilePath(), 'utf-8');
39
+ const content = await fs.readFile(identityPath, 'utf-8');
40
40
  const parsed = JSON.parse(content);
41
- if (parsed.userId && validateUserId(parsed.userId) === true) {
42
- return parsed.userId.trim();
41
+ return parsed.identity || null;
42
+ }
43
+ catch (error) {
44
+ if (error.code === 'ENOENT') {
45
+ return null;
43
46
  }
47
+ // If parse fails or another read error occurs, return null
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Write identity to file with restricted permissions.
53
+ *
54
+ * @param identity - The identity string to write
55
+ */
56
+ export async function writeIdentity(identity) {
57
+ const identityPath = getIdentityPath();
58
+ const identityDir = path.dirname(identityPath);
59
+ // Create directory if it doesn't exist
60
+ await fs.mkdir(identityDir, { recursive: true });
61
+ // Write identity file
62
+ await fs.writeFile(identityPath, JSON.stringify({ identity }, null, 2) + '\n');
63
+ // Set file permissions to 0600 (owner read/write only)
64
+ // This works on Unix-like systems; on Windows it's a no-op
65
+ try {
66
+ await fs.chmod(identityPath, 0o600);
44
67
  }
45
68
  catch {
46
- // Missing or invalid file treat as no identity
69
+ // Windows doesn't support chmod in the same way, but that's okay
70
+ // The file is still protected by OS-level permissions
47
71
  }
48
- return null;
49
72
  }
50
- export async function writeStoredUserId(userId) {
51
- const filePath = getIdentityFilePath();
52
- await fs.mkdir(path.dirname(filePath), { recursive: true });
53
- const payload = { userId: userId.trim() };
54
- await fs.writeFile(filePath, JSON.stringify(payload, null, 2) + '\n', { mode: 0o600 });
55
- cachedUserId = userId.trim();
73
+ /**
74
+ * Validate email format using RFC 5322 compliant pattern.
75
+ *
76
+ * @param email - The email string to validate
77
+ * @returns True if valid, false otherwise
78
+ */
79
+ export function validateEmail(email) {
80
+ // Structural checks first
81
+ if (!email.includes('@'))
82
+ return false;
83
+ const parts = email.split('@');
84
+ if (parts.length !== 2)
85
+ return false;
86
+ if (parts[0].length === 0 || parts[1].length === 0)
87
+ return false;
88
+ // Domain must have at least one dot for a valid email format
89
+ if (!parts[1].includes('.'))
90
+ return false;
91
+ // RFC 5322 compliant email regex pattern
92
+ const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
93
+ return emailRegex.test(email);
56
94
  }
57
- export async function resolveTelemetryUserId(options) {
58
- if (cachedUserId) {
59
- return cachedUserId;
60
- }
61
- const envUser = process.env.OPENSPEC_TELEMETRY_USER?.trim();
62
- if (envUser && validateUserId(envUser) === true) {
63
- cachedUserId = envUser;
64
- return envUser;
65
- }
66
- const stored = await readStoredUserId();
67
- if (stored) {
68
- cachedUserId = stored;
69
- return stored;
70
- }
71
- if (options?.prompt === true) {
72
- try {
73
- return await promptAndStoreTelemetryIdentity();
74
- }
75
- catch (error) {
76
- if (error instanceof TelemetryIdentityRequiredError) {
77
- return null;
78
- }
79
- throw error;
80
- }
81
- }
82
- return null;
95
+ /**
96
+ * Validate username format.
97
+ * Username must be 3-32 characters, alphanumeric + underscores + hyphens.
98
+ *
99
+ * @param username - The username string to validate
100
+ * @returns True if valid, false otherwise
101
+ */
102
+ export function validateUsername(username) {
103
+ const usernameRegex = /^[a-zA-Z0-9_-]{3,32}$/;
104
+ // Ensure @ is not allowed in usernames
105
+ if (username.includes('@'))
106
+ return false;
107
+ return usernameRegex.test(username);
83
108
  }
84
- export async function promptAndStoreTelemetryIdentity() {
85
- const existing = await resolveTelemetryUserId({ prompt: false });
86
- if (existing) {
87
- return existing;
88
- }
89
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
90
- throw new TelemetryIdentityRequiredError();
91
- }
92
- console.log('Codewalla OpenSpec collects usage analytics tied to your email/username.');
93
- const answer = await input({
94
- message: 'Enter your Codewalla email or username:',
95
- validate: validateUserId,
96
- });
97
- await writeStoredUserId(answer);
98
- return answer.trim();
109
+ /**
110
+ * Normalize identity input by trimming whitespace and converting to lowercase.
111
+ *
112
+ * @param identity - The identity string to normalize
113
+ * @returns The normalized identity string
114
+ */
115
+ export function normalizeIdentity(identity) {
116
+ return identity.trim().toLowerCase();
99
117
  }
100
- export async function requireTelemetryIdentity() {
101
- const userId = await resolveTelemetryUserId({ prompt: false });
102
- if (!userId) {
103
- throw new TelemetryIdentityRequiredError();
118
+ /**
119
+ * Validate identity format (email or username).
120
+ *
121
+ * @param identity - The identity string to validate
122
+ * @returns Object with valid flag and error messages if invalid
123
+ */
124
+ export function validateIdentity(identity) {
125
+ const errors = [];
126
+ if (!identity || identity.trim().length === 0) {
127
+ errors.push('Identity cannot be empty');
128
+ return { valid: false, errors };
104
129
  }
105
- return userId;
106
- }
107
- export async function setupTelemetryIdentity(options) {
108
- const existing = await resolveTelemetryUserId({ prompt: false });
109
- if (existing) {
110
- return existing;
130
+ const isEmail = validateEmail(identity);
131
+ const isUsername = validateUsername(identity);
132
+ // Must be either a valid email OR a valid username, not both
133
+ if (!isEmail && !isUsername) {
134
+ errors.push('Identity must be a valid email address or username (3-32 alphanumeric characters, underscores, or hyphens)');
111
135
  }
112
- if (options.interactive) {
113
- return promptAndStoreTelemetryIdentity();
136
+ // If it looks like an email (has @), ensure it's actually a valid email
137
+ if (identity.includes('@') && !isEmail) {
138
+ errors.push('Invalid email format');
114
139
  }
115
- throw new TelemetryIdentityRequiredError();
116
- }
117
- /** @deprecated Use setupTelemetryIdentity or requireTelemetryIdentity */
118
- export async function ensureTelemetryIdentity() {
119
- return resolveTelemetryUserId({ prompt: true });
140
+ // If it doesn't have @ and is not a valid username, reject
141
+ if (!identity.includes('@') && !isUsername) {
142
+ errors.push('Invalid username format');
143
+ }
144
+ return { valid: errors.length === 0, errors };
120
145
  }
121
- /** @internal Test helper */
122
- export function resetIdentityCacheForTests() {
123
- cachedUserId = null;
146
+ /**
147
+ * Interactive prompt for identity collection.
148
+ * Uses inquirer for consistent prompting with other OpenSpec commands.
149
+ *
150
+ * @returns The user's identity input
151
+ */
152
+ export async function promptForIdentity() {
153
+ const { input } = await import('@inquirer/prompts');
154
+ const identity = await input({
155
+ message: 'Enter your identity for telemetry (email or username):',
156
+ validate: (value) => {
157
+ if (!value || value.trim().length === 0) {
158
+ return 'Identity cannot be empty';
159
+ }
160
+ const normalized = normalizeIdentity(value);
161
+ const validation = validateIdentity(normalized);
162
+ if (!validation.valid) {
163
+ return validation.errors[0];
164
+ }
165
+ return true;
166
+ },
167
+ });
168
+ return normalizeIdentity(identity);
124
169
  }
125
170
  //# sourceMappingURL=identity.js.map
@@ -1,17 +1,128 @@
1
- import type { CommandTelemetryContext } from './command-context.js';
2
- export declare function canSendTelemetry(): Promise<boolean>;
3
- export { TelemetryIdentityRequiredError, buildIdentityRequiredMessage, resolveTelemetryUserId, requireTelemetryIdentity, setupTelemetryIdentity, promptAndStoreTelemetryIdentity, getIdentityFilePath, validateUserId, ensureTelemetryIdentity, } from './identity.js';
4
- export { DEFAULT_POSTHOG_KEY, DEFAULT_POSTHOG_HOST, safeTelemetryFetch } from './client.js';
5
- export { sanitizeWorkflowInput, sanitizeTelemetryContent, readSanitizedFile, readWorkflowInputFile, normalizeEditor, resolveWorkflowInputAsync, VALID_EDITORS, MAX_ARTIFACT_BODY_LENGTH, type WorkflowEditor, } from './input.js';
6
- export { resolveTelemetryCommandPath, buildCommandTelemetryContext, type CommandTelemetryContext, type CommandCategory, } from './command-context.js';
7
- export { resolveCaller } from './caller.js';
8
- export declare function trackCommand(commandName: string, version: string, context?: CommandTelemetryContext): Promise<void>;
9
- export declare function trackEvent(event: string, properties?: Record<string, unknown>): Promise<void>;
10
- export declare function trackCommandFailed(command: string, error: unknown, errorCode?: string): Promise<void>;
1
+ /**
2
+ * Check if telemetry is enabled.
3
+ *
4
+ * Identity-based telemetry is mandatory for all commands except init and update.
5
+ * Opt-out mechanisms have been removed.
6
+ *
7
+ * Kept synchronous so call sites need not become async. Reads config via
8
+ * sync getGlobalConfig() rather than async getTelemetryConfig().
9
+ */
10
+ export declare function isTelemetryEnabled(): boolean;
11
+ /**
12
+ * Get the user identity for telemetry.
13
+ * Reads from identity file or environment variable.
14
+ */
15
+ export declare function getIdentity(): Promise<string | null>;
16
+ /**
17
+ * Track a command execution.
18
+ *
19
+ * @param commandName - The command name (e.g., 'init', 'change:apply')
20
+ * @param version - The OpenSpec version
21
+ */
22
+ export declare function trackCommand(commandName: string, version: string): Promise<void>;
23
+ /**
24
+ * Track a comprehension quiz attempt.
25
+ *
26
+ * @param changeName - The name of the change
27
+ * @param attemptNumber - The attempt number
28
+ */
29
+ export declare function trackComprehensionAttempt(changeName: string, attemptNumber: number): Promise<void>;
30
+ /**
31
+ * Track a comprehension quiz completion.
32
+ *
33
+ * @param changeName - The name of the change
34
+ * @param scorePercent - The score percentage (0-100)
35
+ * @param result - The result ('pass' or 'fail')
36
+ * @param attemptNumber - The attempt number
37
+ */
38
+ export declare function trackComprehensionCompletion(changeName: string, scorePercent: number, result: 'pass' | 'fail', attemptNumber: number): Promise<void>;
39
+ /**
40
+ * Track when apply becomes ready after quiz pass.
41
+ *
42
+ * @param changeName - The name of the the change
43
+ */
44
+ export declare function trackApplyReady(changeName: string): Promise<void>;
45
+ /**
46
+ * Track artifact modification request.
47
+ *
48
+ * @param changeName - The name of the change
49
+ * @param artifactId - The artifact being modified
50
+ */
51
+ export declare function trackArtifactModifyRequested(changeName: string, artifactId: string): Promise<void>;
52
+ /**
53
+ * Track artifact revision request (when downstream artifacts need regeneration).
54
+ *
55
+ * @param changeName - The name of the change
56
+ * @param artifactId - The artifact being regenerated
57
+ */
58
+ export declare function trackArtifactRevisionRequested(changeName: string, artifactId: string): Promise<void>;
59
+ /**
60
+ * Track artifact content change.
61
+ *
62
+ * @param changeName - The name of the change
63
+ * @param artifactId - The artifact whose content changed
64
+ */
65
+ export declare function trackArtifactContentChanged(changeName: string, artifactId: string): Promise<void>;
66
+ /**
67
+ * Track workflow started event (when a new change is created).
68
+ *
69
+ * @param changeName - The name of the change
70
+ * @param workflowInput - Optional workflow input data
71
+ */
72
+ export declare function trackWorkflowStarted(changeName: string, workflowInput?: Record<string, unknown>): Promise<void>;
73
+ /**
74
+ * Track change proposal ready event (when proposal artifact is created).
75
+ *
76
+ * @param changeName - The name of the change
77
+ */
78
+ export declare function trackChangeProposalReady(changeName: string): Promise<void>;
79
+ /**
80
+ * Track change archived event (when a change is archived).
81
+ *
82
+ * @param changeName - The name of the change
83
+ */
84
+ export declare function trackChangeArchived(changeName: string): Promise<void>;
85
+ /**
86
+ * Track store operation event.
87
+ *
88
+ * @param operation - The store operation (add, remove, list, register, unregister, doctor)
89
+ * @param storeId - Optional store ID
90
+ */
91
+ export declare function trackStoreOperation(operation: 'add' | 'remove' | 'list' | 'register' | 'unregister' | 'doctor', storeId?: string): Promise<void>;
92
+ /**
93
+ * Track workset operation event.
94
+ *
95
+ * @param operation - The workset operation (create, list, open, remove)
96
+ * @param worksetName - Optional workset name
97
+ */
98
+ export declare function trackWorksetOperation(operation: 'create' | 'list' | 'open' | 'remove', worksetName?: string): Promise<void>;
99
+ /**
100
+ * Track validation operation event.
101
+ *
102
+ * @param changeName - The change name being validated
103
+ * @param result - Validation result (valid, invalid, warnings)
104
+ */
105
+ export declare function trackValidationOperation(changeName: string, result: 'valid' | 'invalid' | 'warnings'): Promise<void>;
106
+ /**
107
+ * Track config change event.
108
+ *
109
+ * @param operation - The config operation (set, unset)
110
+ * @param key - The config key being changed
111
+ */
112
+ export declare function trackConfigChange(operation: 'set' | 'unset', key: string): Promise<void>;
113
+ /**
114
+ * Track feedback submission event.
115
+ *
116
+ * @param category - Feedback category
117
+ */
118
+ export declare function trackFeedbackSubmission(category: string): Promise<void>;
119
+ /**
120
+ * Show first-run telemetry notice if not already seen.
121
+ */
122
+ export declare function maybeShowTelemetryNotice(): Promise<void>;
123
+ /**
124
+ * Flush pending telemetry events.
125
+ * Call this before CLI exit.
126
+ */
11
127
  export declare function shutdown(): Promise<void>;
12
- /** @internal Test helper */
13
- export declare function resetTelemetryForTests(): void;
14
- export { trackWorkflowStarted, maybeEmitProposalReady, maybeEmitApplyReady, trackArtifactInstructions, trackArtifactContentChanges, trackArtifactModifyRequested, trackChangeArchived, buildSpecDeltasFromUpdates, } from './workflow.js';
15
- export { trackComprehensionAttempt, trackComprehensionGateChecked, trackComprehensionRetakeRequired, incrementComprehensionAttempt, incrementComprehensionFailureCount, enrichFromMarker, } from './comprehension.js';
16
- export type { EntryPoint } from './marker.js';
17
128
  //# sourceMappingURL=index.d.ts.map