@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
package/dist/core/view.js CHANGED
@@ -3,6 +3,7 @@ import * as path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { getTaskProgressForChange } from '../utils/task-progress.js';
5
5
  import { MarkdownParser } from './parsers/markdown-parser.js';
6
+ import { discoverSpecFiles } from '../utils/spec-discovery.js';
6
7
  export class ViewCommand {
7
8
  async execute(targetPath = '.') {
8
9
  const openspecDir = path.join(targetPath, 'openspec');
@@ -70,7 +71,7 @@ export class ViewCommand {
70
71
  const entries = fs.readdirSync(changesDir, { withFileTypes: true });
71
72
  for (const entry of entries) {
72
73
  if (entry.isDirectory() && entry.name !== 'archive') {
73
- const progress = await getTaskProgressForChange(changesDir, entry.name);
74
+ const progress = await getTaskProgressForChange(changesDir, entry.name, path.dirname(openspecDir));
74
75
  if (progress.total === 0) {
75
76
  // No tasks defined yet - still in planning/draft phase
76
77
  draft.push({ name: entry.name });
@@ -106,23 +107,17 @@ export class ViewCommand {
106
107
  return [];
107
108
  }
108
109
  const specs = [];
109
- const entries = fs.readdirSync(specsDir, { withFileTypes: true });
110
- for (const entry of entries) {
111
- if (entry.isDirectory()) {
112
- const specFile = path.join(specsDir, entry.name, 'spec.md');
113
- if (fs.existsSync(specFile)) {
114
- try {
115
- const content = fs.readFileSync(specFile, 'utf-8');
116
- const parser = new MarkdownParser(content);
117
- const spec = parser.parseSpec(entry.name);
118
- const requirementCount = spec.requirements.length;
119
- specs.push({ name: entry.name, requirementCount });
120
- }
121
- catch (error) {
122
- // If spec cannot be parsed, include with 0 count
123
- specs.push({ name: entry.name, requirementCount: 0 });
124
- }
125
- }
110
+ for (const { id, specFile } of await discoverSpecFiles(specsDir)) {
111
+ try {
112
+ const content = fs.readFileSync(specFile, 'utf-8');
113
+ const parser = new MarkdownParser(content);
114
+ const spec = parser.parseSpec(id);
115
+ const requirementCount = spec.requirements.length;
116
+ specs.push({ name: id, requirementCount });
117
+ }
118
+ catch (error) {
119
+ // If spec cannot be parsed, include with 0 count
120
+ specs.push({ name: id, requirementCount: 0 });
126
121
  }
127
122
  }
128
123
  return specs;
@@ -113,7 +113,7 @@ async function createSearchableMultiSelect() {
113
113
  const actualIndex = startIndex + i;
114
114
  const isActive = actualIndex === cursor;
115
115
  const selected = selectedSet.has(item.value);
116
- const icon = selected ? chalk.green('') : chalk.dim('');
116
+ const icon = selected ? chalk.green('[x]') : chalk.dim('[ ]');
117
117
  const arrow = isActive ? chalk.cyan('›') : ' ';
118
118
  const name = isActive ? chalk.cyan(item.name) : item.name;
119
119
  const isRefresh = selected && item.configured;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Caller detection module for telemetry.
3
+ *
4
+ * Detects the caller type (human, automation, ci, devin, cursor-agent, windsurf)
5
+ * from environment fingerprints and includes this information in all telemetry events.
6
+ */
7
+ /**
8
+ * Detect the caller type from environment fingerprints.
9
+ *
10
+ * Priority order:
11
+ * 1. Manual override via OPENSPEC_CALLER environment variable
12
+ * 2. Agent-specific detection (Devin, Cursor, Windsurf)
13
+ * 3. CI environment detection
14
+ * 4. TTY detection (interactive terminal)
15
+ * 5. Default to human if TTY present, else automation
16
+ *
17
+ * @returns The detected caller type
18
+ */
19
+ export declare function detectCaller(): string;
20
+ /**
21
+ * Validate a caller type against the allowed set.
22
+ *
23
+ * @param callerType - The caller type to validate
24
+ * @returns True if valid, false otherwise
25
+ */
26
+ export declare function isValidCallerType(callerType: string): boolean;
27
+ //# sourceMappingURL=caller-detection.d.ts.map
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Caller detection module for telemetry.
3
+ *
4
+ * Detects the caller type (human, automation, ci, devin, cursor-agent, windsurf)
5
+ * from environment fingerprints and includes this information in all telemetry events.
6
+ */
7
+ import { isCiEnvironment } from '../utils/ci.js';
8
+ // Allowed caller types
9
+ const ALLOWED_CALLER_TYPES = new Set([
10
+ 'human',
11
+ 'automation',
12
+ 'ci',
13
+ 'devin',
14
+ 'cursor-agent',
15
+ 'windsurf',
16
+ ]);
17
+ /**
18
+ * Detect the caller type from environment fingerprints.
19
+ *
20
+ * Priority order:
21
+ * 1. Manual override via OPENSPEC_CALLER environment variable
22
+ * 2. Agent-specific detection (Devin, Cursor, Windsurf)
23
+ * 3. CI environment detection
24
+ * 4. TTY detection (interactive terminal)
25
+ * 5. Default to human if TTY present, else automation
26
+ *
27
+ * @returns The detected caller type
28
+ */
29
+ export function detectCaller() {
30
+ // 1. Manual override (highest priority)
31
+ const manualOverride = process.env.OPENSPEC_CALLER;
32
+ if (manualOverride) {
33
+ const normalized = manualOverride.trim().toLowerCase();
34
+ if (ALLOWED_CALLER_TYPES.has(normalized)) {
35
+ return normalized;
36
+ }
37
+ // Invalid override, fall through to auto-detection
38
+ }
39
+ // 2. Agent-specific detection (placeholders for research)
40
+ // TODO: Research actual environment variable fingerprints for Devin, Cursor, Windsurf
41
+ const agentType = detectAgent();
42
+ if (agentType) {
43
+ return agentType;
44
+ }
45
+ // 3. CI environment detection
46
+ if (isCiEnvironment()) {
47
+ return 'ci';
48
+ }
49
+ // 4. TTY detection
50
+ if (process.stdout.isTTY) {
51
+ return 'human';
52
+ }
53
+ // 5. Default to automation
54
+ return 'automation';
55
+ }
56
+ /**
57
+ * Detect AI coding agent from environment variables.
58
+ *
59
+ * @returns The agent type if detected, null otherwise
60
+ */
61
+ function detectAgent() {
62
+ // TODO: Research and implement actual environment variable fingerprints
63
+ // for Devin, Cursor, and Windsurf agents
64
+ // Placeholder implementations (to be replaced with actual detection logic)
65
+ if (process.env.DEVIN_AGENT === 'true') {
66
+ return 'devin';
67
+ }
68
+ if (process.env.CURSOR_AGENT === 'true') {
69
+ return 'cursor-agent';
70
+ }
71
+ if (process.env.WINDSURF_AGENT === 'true') {
72
+ return 'windsurf';
73
+ }
74
+ return null;
75
+ }
76
+ /**
77
+ * Validate a caller type against the allowed set.
78
+ *
79
+ * @param callerType - The caller type to validate
80
+ * @returns True if valid, false otherwise
81
+ */
82
+ export function isValidCallerType(callerType) {
83
+ return ALLOWED_CALLER_TYPES.has(callerType.trim().toLowerCase());
84
+ }
85
+ //# sourceMappingURL=caller-detection.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for caller detection module.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=caller-detection.test.d.ts.map
@@ -1,11 +1,57 @@
1
+ import { type TelemetryConfig } from '../core/global-config.js';
1
2
  export declare const CONFIG_DIR_NAME = "openspec";
2
3
  export declare const CONFIG_FILE_NAME = "config.json";
4
+ export declare const IDENTITY_FILE_NAME = "telemetry-identity.json";
5
+ export declare const DEFAULT_POSTHOG_API_KEY = "phc_s56WNC4SgBSQBqa5jgZ22MpCmxv5rUsAy4g6MikQaZtD";
6
+ export declare const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
7
+ /** Re-export shared telemetry section type (single source of truth in global-config). */
8
+ export type { TelemetryConfig };
3
9
  export interface GlobalConfig {
10
+ telemetry?: TelemetryConfig;
4
11
  [key: string]: unknown;
5
12
  }
13
+ /**
14
+ * Get the path to the global config file.
15
+ * Follows XDG Base Directory Specification and platform conventions.
16
+ *
17
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
18
+ * - Unix/macOS fallback: ~/.config/openspec/
19
+ * - Windows fallback: %APPDATA%/openspec/
20
+ */
6
21
  export declare function getConfigPath(): string;
22
+ /**
23
+ * Get the path to the identity file.
24
+ * Follows platform-specific config directory conventions.
25
+ *
26
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
27
+ * - Unix/macOS fallback: ~/.config/openspec/
28
+ * - Windows fallback: %APPDATA%/openspec/
29
+ */
30
+ export declare function getIdentityFilePath(): string;
31
+ /**
32
+ * Read the global config file.
33
+ * Returns an empty object if the file doesn't exist.
34
+ */
7
35
  export declare function readConfig(): Promise<GlobalConfig>;
36
+ /**
37
+ * Write to the global config file.
38
+ * Preserves existing fields and merges in new values.
39
+ */
8
40
  export declare function writeConfig(updates: Partial<GlobalConfig>): Promise<void>;
9
- export declare function getTelemetryConfig(): Promise<Record<string, unknown>>;
10
- export declare function updateTelemetryConfig(updates: Record<string, unknown>): Promise<void>;
41
+ /**
42
+ * Get the telemetry config section.
43
+ */
44
+ export declare function getTelemetryConfig(): Promise<TelemetryConfig>;
45
+ /**
46
+ * Get the PostHog API key from environment variable or default.
47
+ */
48
+ export declare function getPostHogApiKey(): string;
49
+ /**
50
+ * Get the PostHog host from environment variable or default.
51
+ */
52
+ export declare function getPostHogHost(): string;
53
+ /**
54
+ * Update the telemetry config section.
55
+ */
56
+ export declare function updateTelemetryConfig(updates: Partial<TelemetryConfig>): Promise<void>;
11
57
  //# sourceMappingURL=config.d.ts.map
@@ -1,15 +1,24 @@
1
1
  /**
2
- * Global configuration for OpenSpec CLI state.
3
- * Stores settings in the platform-appropriate config directory.
2
+ * Global configuration for telemetry state.
3
+ * Stores anonymous ID and notice-seen flag in the platform-appropriate config directory.
4
4
  */
5
5
  import { promises as fs } from 'fs';
6
6
  import path from 'path';
7
+ import os from 'os';
7
8
  import { GLOBAL_CONFIG_DIR_NAME, GLOBAL_CONFIG_FILE_NAME, getGlobalConfigDir, } from '../core/global-config.js';
9
+ // Constants
8
10
  export const CONFIG_DIR_NAME = GLOBAL_CONFIG_DIR_NAME;
9
11
  export const CONFIG_FILE_NAME = GLOBAL_CONFIG_FILE_NAME;
12
+ export const IDENTITY_FILE_NAME = 'telemetry-identity.json';
13
+ // PostHog configuration defaults
14
+ export const DEFAULT_POSTHOG_API_KEY = 'phc_s56WNC4SgBSQBqa5jgZ22MpCmxv5rUsAy4g6MikQaZtD';
15
+ export const DEFAULT_POSTHOG_HOST = 'https://us.i.posthog.com';
10
16
  function getConfigDir() {
11
17
  return getGlobalConfigDir();
12
18
  }
19
+ function getLegacyConfigPath() {
20
+ return path.join(os.homedir(), '.config', CONFIG_DIR_NAME, CONFIG_FILE_NAME);
21
+ }
13
22
  async function readConfigFile(configPath) {
14
23
  try {
15
24
  const content = await fs.readFile(configPath, 'utf-8');
@@ -19,6 +28,7 @@ async function readConfigFile(configPath) {
19
28
  if (error.code === 'ENOENT') {
20
29
  return { status: 'missing' };
21
30
  }
31
+ // If parse fails or another read error occurs, ignore the file.
22
32
  return { status: 'invalid', config: {} };
23
33
  }
24
34
  }
@@ -26,31 +36,123 @@ async function writeConfigFile(configPath, config) {
26
36
  await fs.mkdir(path.dirname(configPath), { recursive: true });
27
37
  await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
28
38
  }
39
+ function hasMissingTelemetryFields(config) {
40
+ const telemetry = config.telemetry;
41
+ return (!telemetry ||
42
+ telemetry.noticeSeen === undefined);
43
+ }
44
+ function mergeLegacyTelemetry(config, legacyConfig) {
45
+ const legacyTelemetry = legacyConfig.telemetry;
46
+ if (!legacyTelemetry) {
47
+ return undefined;
48
+ }
49
+ const currentTelemetry = config.telemetry ?? {};
50
+ const shouldMigrate = (currentTelemetry.noticeSeen === undefined && legacyTelemetry.noticeSeen !== undefined);
51
+ if (!shouldMigrate) {
52
+ return undefined;
53
+ }
54
+ return {
55
+ ...config,
56
+ telemetry: {
57
+ ...legacyTelemetry,
58
+ ...currentTelemetry,
59
+ },
60
+ };
61
+ }
62
+ async function migrateLegacyTelemetryConfig(configPath, config, persist) {
63
+ const legacyConfigPath = getLegacyConfigPath();
64
+ if (path.resolve(configPath) === path.resolve(legacyConfigPath) || !hasMissingTelemetryFields(config)) {
65
+ return config;
66
+ }
67
+ const legacyRead = await readConfigFile(legacyConfigPath);
68
+ if (legacyRead.status !== 'ok') {
69
+ return config;
70
+ }
71
+ const migrated = mergeLegacyTelemetry(config, legacyRead.config);
72
+ if (!migrated) {
73
+ return config;
74
+ }
75
+ if (persist) {
76
+ try {
77
+ await writeConfigFile(configPath, migrated);
78
+ }
79
+ catch {
80
+ // Preserve telemetry for this run even if the one-time migration cannot be persisted.
81
+ }
82
+ }
83
+ return migrated;
84
+ }
85
+ /**
86
+ * Get the path to the global config file.
87
+ * Follows XDG Base Directory Specification and platform conventions.
88
+ *
89
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
90
+ * - Unix/macOS fallback: ~/.config/openspec/
91
+ * - Windows fallback: %APPDATA%/openspec/
92
+ */
29
93
  export function getConfigPath() {
30
- return path.join(getConfigDir(), CONFIG_FILE_NAME);
94
+ const configDir = getConfigDir();
95
+ return path.join(configDir, CONFIG_FILE_NAME);
31
96
  }
97
+ /**
98
+ * Get the path to the identity file.
99
+ * Follows platform-specific config directory conventions.
100
+ *
101
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
102
+ * - Unix/macOS fallback: ~/.config/openspec/
103
+ * - Windows fallback: %APPDATA%/openspec/
104
+ */
105
+ export function getIdentityFilePath() {
106
+ const configDir = getConfigDir();
107
+ return path.join(configDir, IDENTITY_FILE_NAME);
108
+ }
109
+ /**
110
+ * Read the global config file.
111
+ * Returns an empty object if the file doesn't exist.
112
+ */
32
113
  export async function readConfig() {
33
114
  const configPath = getConfigPath();
34
115
  const read = await readConfigFile(configPath);
35
- return read.status === 'ok' ? read.config : {};
116
+ const config = read.status === 'ok' ? read.config : {};
117
+ return migrateLegacyTelemetryConfig(configPath, config, read.status !== 'invalid');
36
118
  }
119
+ /**
120
+ * Write to the global config file.
121
+ * Preserves existing fields and merges in new values.
122
+ */
37
123
  export async function writeConfig(updates) {
38
124
  const configPath = getConfigPath();
125
+ // Read existing config and merge
39
126
  const existing = await readConfig();
40
127
  const merged = { ...existing, ...updates };
41
- if (updates.telemetry && existing.telemetry && typeof existing.telemetry === 'object') {
42
- merged.telemetry = {
43
- ...existing.telemetry,
44
- ...updates.telemetry,
45
- };
128
+ // Deep merge for telemetry object
129
+ if (updates.telemetry && existing.telemetry) {
130
+ merged.telemetry = { ...existing.telemetry, ...updates.telemetry };
46
131
  }
47
132
  await writeConfigFile(configPath, merged);
48
133
  }
134
+ /**
135
+ * Get the telemetry config section.
136
+ */
49
137
  export async function getTelemetryConfig() {
50
138
  const config = await readConfig();
51
- const telemetry = config.telemetry;
52
- return telemetry && typeof telemetry === 'object' ? telemetry : {};
139
+ return config.telemetry ?? {};
53
140
  }
141
+ /**
142
+ * Get the PostHog API key from environment variable or default.
143
+ */
144
+ export function getPostHogApiKey() {
145
+ return process.env.POSTHOG_API_KEY || DEFAULT_POSTHOG_API_KEY;
146
+ }
147
+ /**
148
+ * Get the PostHog host from environment variable or default.
149
+ */
150
+ export function getPostHogHost() {
151
+ return process.env.POSTHOG_HOST || DEFAULT_POSTHOG_HOST;
152
+ }
153
+ /**
154
+ * Update the telemetry config section.
155
+ */
54
156
  export async function updateTelemetryConfig(updates) {
55
157
  const existing = await getTelemetryConfig();
56
158
  await writeConfig({
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for PostHog configuration.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Event deduplication module for telemetry.
3
+ *
4
+ * Prevents duplicate event emission through:
5
+ * - Time-based throttling for $identify calls (24h window)
6
+ * - State-based deduplication for gate events
7
+ * - Rapid duplicate prevention for general events
8
+ */
9
+ /**
10
+ * Check if an $identify call should be throttled for a given user.
11
+ *
12
+ * @param userId - The anonymous user ID
13
+ * @returns True if the call should be throttled (not sent), false otherwise
14
+ */
15
+ export declare function shouldThrottleIdentify(userId: string): boolean;
16
+ /**
17
+ * Check if a gate event should be deduplicated based on state comparison.
18
+ *
19
+ * @param eventKey - Unique key for the gate event (e.g., "comprehension_gate_checked:change-name")
20
+ * @param currentState - The current state of the gate
21
+ * @returns True if the event should be deduplicated (not sent), false otherwise
22
+ */
23
+ export declare function shouldDeduplicateGateEvent(eventKey: string, currentState: string): boolean;
24
+ /**
25
+ * Check if a general event should be deduplicated due to rapid succession.
26
+ *
27
+ * @param eventKey - Unique key for the event (e.g., "command_executed:init")
28
+ * @returns True if the event should be deduplicated (not sent), false otherwise
29
+ */
30
+ export declare function shouldDeduplicateRapidEvent(eventKey: string): boolean;
31
+ /**
32
+ * Clear deduplication state (useful for testing or session reset).
33
+ */
34
+ export declare function clearDeduplicationState(): void;
35
+ //# sourceMappingURL=deduplication.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Event deduplication module for telemetry.
3
+ *
4
+ * Prevents duplicate event emission through:
5
+ * - Time-based throttling for $identify calls (24h window)
6
+ * - State-based deduplication for gate events
7
+ * - Rapid duplicate prevention for general events
8
+ */
9
+ // In-memory state for session-level deduplication
10
+ const identifyThrottleState = new Map();
11
+ const gateEventState = new Map();
12
+ const recentEvents = new Map();
13
+ const IDENTIFY_THROTTLE_MS = 24 * 60 * 60 * 1000; // 24 hours
14
+ const RAPID_DUPLICATE_WINDOW_MS = 1000; // 1 second
15
+ /**
16
+ * Check if an $identify call should be throttled for a given user.
17
+ *
18
+ * @param userId - The anonymous user ID
19
+ * @returns True if the call should be throttled (not sent), false otherwise
20
+ */
21
+ export function shouldThrottleIdentify(userId) {
22
+ const now = Date.now();
23
+ const lastIdentify = identifyThrottleState.get(userId);
24
+ if (lastIdentify === undefined) {
25
+ identifyThrottleState.set(userId, now);
26
+ return false;
27
+ }
28
+ const timeSinceLastIdentify = now - lastIdentify;
29
+ if (timeSinceLastIdentify < IDENTIFY_THROTTLE_MS) {
30
+ return true; // Throttle
31
+ }
32
+ identifyThrottleState.set(userId, now);
33
+ return false;
34
+ }
35
+ /**
36
+ * Check if a gate event should be deduplicated based on state comparison.
37
+ *
38
+ * @param eventKey - Unique key for the gate event (e.g., "comprehension_gate_checked:change-name")
39
+ * @param currentState - The current state of the gate
40
+ * @returns True if the event should be deduplicated (not sent), false otherwise
41
+ */
42
+ export function shouldDeduplicateGateEvent(eventKey, currentState) {
43
+ const previousState = gateEventState.get(eventKey);
44
+ if (previousState === undefined) {
45
+ gateEventState.set(eventKey, currentState);
46
+ return false;
47
+ }
48
+ if (previousState === currentState) {
49
+ return true; // Deduplicate - state unchanged
50
+ }
51
+ gateEventState.set(eventKey, currentState);
52
+ return false;
53
+ }
54
+ /**
55
+ * Check if a general event should be deduplicated due to rapid succession.
56
+ *
57
+ * @param eventKey - Unique key for the event (e.g., "command_executed:init")
58
+ * @returns True if the event should be deduplicated (not sent), false otherwise
59
+ */
60
+ export function shouldDeduplicateRapidEvent(eventKey) {
61
+ const now = Date.now();
62
+ const lastEventTime = recentEvents.get(eventKey);
63
+ if (lastEventTime === undefined) {
64
+ recentEvents.set(eventKey, now);
65
+ return false;
66
+ }
67
+ const timeSinceLastEvent = now - lastEventTime;
68
+ if (timeSinceLastEvent < RAPID_DUPLICATE_WINDOW_MS) {
69
+ return true; // Deduplicate - too rapid
70
+ }
71
+ recentEvents.set(eventKey, now);
72
+ return false;
73
+ }
74
+ /**
75
+ * Clear deduplication state (useful for testing or session reset).
76
+ */
77
+ export function clearDeduplicationState() {
78
+ identifyThrottleState.clear();
79
+ gateEventState.clear();
80
+ recentEvents.clear();
81
+ }
82
+ //# sourceMappingURL=deduplication.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for deduplication module.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=deduplication.test.d.ts.map
@@ -1,23 +1,63 @@
1
- export declare const IDENTITY_FILENAME = "telemetry-identity.json";
2
- export declare class TelemetryIdentityRequiredError extends Error {
3
- readonly code = "telemetry_identity_required";
4
- constructor(message?: string);
5
- }
6
- export declare function buildIdentityRequiredMessage(): string;
7
- export declare function validateUserId(value: string): true | string;
8
- export declare function getIdentityFilePath(): string;
9
- export declare function readStoredUserId(): Promise<string | null>;
10
- export declare function writeStoredUserId(userId: string): Promise<void>;
11
- export declare function resolveTelemetryUserId(options?: {
12
- prompt?: boolean;
13
- }): Promise<string | null>;
14
- export declare function promptAndStoreTelemetryIdentity(): Promise<string>;
15
- export declare function requireTelemetryIdentity(): Promise<string>;
16
- export declare function setupTelemetryIdentity(options: {
17
- interactive: boolean;
18
- }): Promise<string>;
19
- /** @deprecated Use setupTelemetryIdentity or requireTelemetryIdentity */
20
- export declare function ensureTelemetryIdentity(): Promise<string | null>;
21
- /** @internal Test helper */
22
- export declare function resetIdentityCacheForTests(): void;
1
+ export declare const IDENTITY_FILE_NAME = "telemetry-identity.json";
2
+ /**
3
+ * Get the path to the identity file.
4
+ * Follows platform-specific config directory conventions.
5
+ *
6
+ * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
7
+ * - Unix/macOS fallback: ~/.config/openspec/
8
+ * - Windows fallback: %APPDATA%/openspec/
9
+ */
10
+ export declare function getIdentityPath(): string;
11
+ /**
12
+ * Read identity from file or environment variable.
13
+ * Environment variable takes precedence over file.
14
+ *
15
+ * @returns The identity string, or null if not configured
16
+ */
17
+ export declare function readIdentity(): Promise<string | null>;
18
+ /**
19
+ * Write identity to file with restricted permissions.
20
+ *
21
+ * @param identity - The identity string to write
22
+ */
23
+ export declare function writeIdentity(identity: string): Promise<void>;
24
+ /**
25
+ * Validate email format using RFC 5322 compliant pattern.
26
+ *
27
+ * @param email - The email string to validate
28
+ * @returns True if valid, false otherwise
29
+ */
30
+ export declare function validateEmail(email: string): boolean;
31
+ /**
32
+ * Validate username format.
33
+ * Username must be 3-32 characters, alphanumeric + underscores + hyphens.
34
+ *
35
+ * @param username - The username string to validate
36
+ * @returns True if valid, false otherwise
37
+ */
38
+ export declare function validateUsername(username: string): boolean;
39
+ /**
40
+ * Normalize identity input by trimming whitespace and converting to lowercase.
41
+ *
42
+ * @param identity - The identity string to normalize
43
+ * @returns The normalized identity string
44
+ */
45
+ export declare function normalizeIdentity(identity: string): string;
46
+ /**
47
+ * Validate identity format (email or username).
48
+ *
49
+ * @param identity - The identity string to validate
50
+ * @returns Object with valid flag and error messages if invalid
51
+ */
52
+ export declare function validateIdentity(identity: string): {
53
+ valid: boolean;
54
+ errors: string[];
55
+ };
56
+ /**
57
+ * Interactive prompt for identity collection.
58
+ * Uses inquirer for consistent prompting with other OpenSpec commands.
59
+ *
60
+ * @returns The user's identity input
61
+ */
62
+ export declare function promptForIdentity(): Promise<string>;
23
63
  //# sourceMappingURL=identity.d.ts.map