@codewalla_india/openspec 1.3.1 → 1.3.3

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 +214 -133
  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 +35 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +69 -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 +98 -0
  41. package/dist/comprehension-quiz/providers/interface.js +150 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +35 -0
  43. package/dist/comprehension-quiz/providers/local.js +69 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +35 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +69 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +35 -0
  47. package/dist/comprehension-quiz/providers/openai.js +72 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +117 -0
  49. package/dist/comprehension-quiz/question-generator.js +203 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +75 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +181 -0
  52. package/dist/comprehension-quiz/types.d.ts +137 -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 +193 -71
  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
@@ -14,10 +14,6 @@ export interface NewChangeOptions {
14
14
  storePath?: string;
15
15
  initiative?: string;
16
16
  areas?: string;
17
- entryPoint?: string;
18
- workflowInput?: string;
19
- workflowInputFile?: string;
20
- editor?: string;
21
17
  json?: boolean;
22
18
  }
23
19
  export declare function newChangeCommand(name: string | undefined, options: NewChangeOptions): Promise<void>;
@@ -8,19 +8,11 @@
8
8
  */
9
9
  import ora from 'ora';
10
10
  import path from 'path';
11
- import { trackWorkflowStarted, trackCommandFailed, normalizeEditor, resolveWorkflowInputAsync, } from '../../telemetry/index.js';
12
11
  import { createChange, validateChangeName } from '../../utils/change-utils.js';
13
12
  import { formatChangeLocation } from '../../core/planning-home.js';
14
13
  import { resolveRootForCommand, RootSelectionError, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
15
14
  import { printJson, statusFromError, validateSchemaExists } from './shared.js';
16
- const VALID_ENTRY_POINTS = new Set(['propose', 'new', 'ff', 'manual']);
17
- function resolveEntryPoint(value) {
18
- const normalized = (value ?? 'manual').toLowerCase();
19
- if (VALID_ENTRY_POINTS.has(normalized)) {
20
- return normalized;
21
- }
22
- throw new Error(`Invalid --entry-point "${value}". Use: propose, new, ff, or manual.`);
23
- }
15
+ import { trackWorkflowStarted } from '../../telemetry/index.js';
24
16
  // -----------------------------------------------------------------------------
25
17
  // Command Implementation
26
18
  // -----------------------------------------------------------------------------
@@ -33,6 +25,8 @@ function assertRemovedOptionsAbsent(options) {
33
25
  }
34
26
  }
35
27
  function printCreatedChangeHuman(payload, root) {
28
+ // A relative path is only honest when the root is where the user
29
+ // stands; a distant ancestor root gets the absolute path.
36
30
  const location = !isStoreSelectedRoot(root) && root.path === process.cwd()
37
31
  ? formatChangeLocation(toPlanningHome(root), payload.change.id)
38
32
  : payload.change.path;
@@ -59,12 +53,7 @@ export async function newChangeCommand(name, options) {
59
53
  return;
60
54
  }
61
55
  const projectRoot = root.path;
62
- const entryPoint = resolveEntryPoint(options.entryPoint);
63
- const editor = normalizeEditor(options.editor);
64
- const workflowInput = await resolveWorkflowInputAsync({
65
- workflowInput: options.workflowInput,
66
- workflowInputFile: options.workflowInputFile,
67
- });
56
+ // Validate schema if provided
68
57
  if (options.schema) {
69
58
  validateSchemaExists(options.schema, projectRoot);
70
59
  }
@@ -80,23 +69,12 @@ export async function newChangeCommand(name, options) {
80
69
  ...(options.goal ? { goal: options.goal } : {}),
81
70
  },
82
71
  });
72
+ // If description provided, create README.md with description
83
73
  if (options.description) {
84
74
  const { promises: fs } = await import('fs');
85
75
  const readmePath = path.join(result.changeDir, 'README.md');
86
76
  await fs.writeFile(readmePath, `# ${name}\n\n${options.description}\n`, 'utf-8');
87
77
  }
88
- await trackWorkflowStarted({
89
- changeDir: result.changeDir,
90
- changeName: name,
91
- schema: result.schema,
92
- entryPoint,
93
- storeSelected: isStoreSelectedRoot(root),
94
- projectRoot,
95
- workflowInput,
96
- description: options.description,
97
- goal: options.goal,
98
- editor,
99
- });
100
78
  const payload = {
101
79
  change: {
102
80
  id: name,
@@ -112,9 +90,13 @@ export async function newChangeCommand(name, options) {
112
90
  }
113
91
  spinner?.stop();
114
92
  printCreatedChangeHuman(payload, root);
93
+ // Track workflow started event
94
+ await trackWorkflowStarted(name, {
95
+ schema: resolvedSchema,
96
+ ...(options.goal ? { goal: options.goal } : {}),
97
+ });
115
98
  }
116
99
  catch (error) {
117
- await trackCommandFailed('new_change', error);
118
100
  spinner?.stop();
119
101
  if (options.json) {
120
102
  printJson({
@@ -17,19 +17,6 @@ export interface TaskItem {
17
17
  description: string;
18
18
  done: boolean;
19
19
  }
20
- export interface ApplyComprehensionInfo {
21
- required: boolean;
22
- passed: boolean;
23
- thresholdPercent: number;
24
- bestScorePercent?: number;
25
- questionCount: number;
26
- questionAllocation: Record<string, number>;
27
- optionsPerQuestion: number;
28
- requirementCount: number;
29
- scenarioCount: number;
30
- pendingTaskCount: number;
31
- attempts?: number;
32
- }
33
20
  export interface ApplyInstructions {
34
21
  changeName: string;
35
22
  changeDir: string;
@@ -41,16 +28,44 @@ export interface ApplyInstructions {
41
28
  remaining: number;
42
29
  };
43
30
  tasks: TaskItem[];
44
- state: 'blocked' | 'all_done' | 'ready';
31
+ state: 'blocked' | 'all_done' | 'ready' | 'comprehension_blocked';
45
32
  missingArtifacts?: string[];
46
- missingComprehension?: boolean;
47
- comprehension?: ApplyComprehensionInfo;
48
33
  instruction: string;
49
34
  /** Referenced-store index (read-only upstream context; omitted when none declared) */
50
35
  references?: ReferenceIndexEntry[];
51
- /** True when apply_ready telemetry was emitted during this generation */
36
+ /** Current project background from the selected root. */
37
+ context?: string;
38
+ /** Current advisory guidance for apply. */
39
+ operationGuidance?: string[];
40
+ /** Comprehension quiz status */
41
+ comprehension?: {
42
+ required: boolean;
43
+ passed: boolean;
44
+ thresholdPercent: number;
45
+ questionCount: number;
46
+ questionAllocation: {
47
+ plan: number;
48
+ specs: number;
49
+ design: number;
50
+ proposal: number;
51
+ tasks: number;
52
+ };
53
+ optionsPerQuestion: number;
54
+ requirementCount: number;
55
+ scenarioCount: number;
56
+ pendingTaskCount: number;
57
+ attempts?: number;
58
+ };
59
+ /** Whether apply ready event was emitted */
52
60
  applyReadyEmitted?: boolean;
53
61
  }
62
+ export interface ArchiveInstructions {
63
+ changeName: string;
64
+ /** Current project background from the selected root. */
65
+ context?: string;
66
+ /** Current advisory guidance for archive. */
67
+ operationGuidance?: string[];
68
+ }
54
69
  export declare const DEFAULT_SCHEMA = "spec-driven";
55
70
  export declare function printJson(payload: unknown): void;
56
71
  export declare function statusFromError(error: unknown): ChangeCommandStatus;
@@ -61,11 +76,11 @@ export declare function isColorDisabled(): boolean;
61
76
  /**
62
77
  * Gets the color function based on status.
63
78
  */
64
- export declare function getStatusColor(status: 'done' | 'ready' | 'blocked'): (text: string) => string;
79
+ export declare function getStatusColor(status: 'done' | 'skipped' | 'ready' | 'blocked'): (text: string) => string;
65
80
  /**
66
81
  * Gets the status indicator for an artifact.
67
82
  */
68
- export declare function getStatusIndicator(status: 'done' | 'ready' | 'blocked'): string;
83
+ export declare function getStatusIndicator(status: 'done' | 'skipped' | 'ready' | 'blocked'): string;
69
84
  /**
70
85
  * Returns the list of available change directory names under openspec/changes/.
71
86
  * Excludes the archive directory and hidden directories.
@@ -9,7 +9,6 @@ import path from 'path';
9
9
  import * as fs from 'fs';
10
10
  import { getSchemaDir, listSchemas } from '../../core/artifact-graph/index.js';
11
11
  import { isRootSelectionError } from '../../core/root-selection.js';
12
- import { validateChangeName } from '../../utils/change-utils.js';
13
12
  // -----------------------------------------------------------------------------
14
13
  // Constants
15
14
  // -----------------------------------------------------------------------------
@@ -46,6 +45,8 @@ export function getStatusColor(status) {
46
45
  switch (status) {
47
46
  case 'done':
48
47
  return chalk.green;
48
+ case 'skipped':
49
+ return chalk.gray;
49
50
  case 'ready':
50
51
  return chalk.yellow;
51
52
  case 'blocked':
@@ -60,6 +61,8 @@ export function getStatusIndicator(status) {
60
61
  switch (status) {
61
62
  case 'done':
62
63
  return color('[x]');
64
+ case 'skipped':
65
+ return color('[~]');
63
66
  case 'ready':
64
67
  return color('[ ]');
65
68
  case 'blocked':
@@ -84,6 +87,33 @@ export async function getAvailableChanges(projectRoot, changesDir = path.join(pr
84
87
  throw error;
85
88
  }
86
89
  }
90
+ /**
91
+ * Validates a change name used to look up an existing change directory.
92
+ * Lookup accepts any directory name that `getAvailableChanges` could return
93
+ * (the kebab-case convention in `validateChangeName` applies at creation
94
+ * time only); it only rejects names that would escape the changes directory
95
+ * or address entries `getAvailableChanges` excludes (hidden dirs, archive).
96
+ *
97
+ * @returns An error message, or undefined if the name is safe to look up
98
+ */
99
+ function validateChangeLookupName(changeName) {
100
+ if (changeName === '.' || changeName === '..') {
101
+ return 'Change name cannot be a relative path segment';
102
+ }
103
+ if (changeName.includes('/') || changeName.includes('\\')) {
104
+ return 'Change name cannot contain path separators';
105
+ }
106
+ if (changeName.includes('\0')) {
107
+ return 'Change name cannot contain null characters';
108
+ }
109
+ if (changeName.startsWith('.')) {
110
+ return 'Change name cannot start with a dot';
111
+ }
112
+ if (changeName === 'archive') {
113
+ return "'archive' is reserved for archived changes";
114
+ }
115
+ return undefined;
116
+ }
87
117
  /**
88
118
  * Validates that a change exists and returns available changes if not.
89
119
  * Checks directory existence directly to support scaffolded changes (without proposal.md).
@@ -100,9 +130,9 @@ export async function validateChangeExists(changeName, projectRoot, changesDir =
100
130
  throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
101
131
  }
102
132
  // Validate change name format to prevent path traversal
103
- const nameValidation = validateChangeName(changeName);
104
- if (!nameValidation.valid) {
105
- throw new Error(`Invalid change name '${changeName}': ${nameValidation.error}`);
133
+ const lookupError = validateChangeLookupName(changeName);
134
+ if (lookupError) {
135
+ throw new Error(`Invalid change name '${changeName}': ${lookupError}`);
106
136
  }
107
137
  // Check directory existence directly
108
138
  const changePath = path.join(changesDir, changeName);
@@ -7,8 +7,7 @@ import ora from 'ora';
7
7
  import chalk from 'chalk';
8
8
  import { getChangeDir } from '../../core/planning-home.js';
9
9
  import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
10
- import { loadChangeContext, formatChangeStatus, resolveArtifactOutputs, resolveSchema, } from '../../core/artifact-graph/index.js';
11
- import { maybeEmitProposalReady, trackArtifactContentChanges, } from '../../telemetry/index.js';
10
+ import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
12
11
  import { validateChangeExists, validateSchemaExists, getAvailableChanges, getStatusIndicator, getStatusColor, } from './shared.js';
13
12
  // -----------------------------------------------------------------------------
14
13
  // Command Implementation
@@ -54,32 +53,6 @@ export async function statusCommand(options) {
54
53
  planningHome,
55
54
  });
56
55
  const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
57
- const changeDir = getChangeDir(planningHome, changeName);
58
- const schema = resolveSchema(context.schemaName, projectRoot);
59
- const contextFiles = {};
60
- for (const artifact of schema.artifacts) {
61
- const outputs = resolveArtifactOutputs(changeDir, artifact.generates);
62
- if (outputs.length > 0) {
63
- contextFiles[artifact.id] = outputs;
64
- }
65
- }
66
- const applyConfig = schema.apply;
67
- const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
68
- const missingArtifacts = [];
69
- for (const artifactId of requiredArtifactIds) {
70
- const artifact = schema.artifacts.find((a) => a.id === artifactId);
71
- if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
72
- missingArtifacts.push(artifactId);
73
- }
74
- }
75
- await trackArtifactContentChanges({ changeDir, changeName, contextFiles });
76
- await maybeEmitProposalReady({
77
- changeDir,
78
- changeName,
79
- schema: context.schemaName,
80
- missingArtifacts,
81
- artifactCount: status.artifacts.length,
82
- });
83
56
  spinner?.stop();
84
57
  if (options.json) {
85
58
  console.log(JSON.stringify({ ...status, root: rootOutput }, null, 2));
@@ -94,26 +67,31 @@ export async function statusCommand(options) {
94
67
  }
95
68
  export function printStatusText(status) {
96
69
  const doneCount = status.artifacts.filter((a) => a.status === 'done').length;
97
- const total = status.artifacts.length;
70
+ const skippedCount = status.artifacts.filter((a) => a.status === 'skipped').length;
71
+ const total = status.artifacts.length - skippedCount;
98
72
  console.log(`Change: ${status.changeName}`);
99
73
  console.log(`Schema: ${status.schemaName}`);
100
74
  if (status.changeRoot) {
101
75
  console.log(`Change root: ${status.changeRoot}`);
102
76
  }
103
- console.log(`Progress: ${doneCount}/${total} artifacts complete`);
77
+ const skippedSuffix = skippedCount > 0 ? ` (${skippedCount} skipped)` : '';
78
+ console.log(`Progress: ${doneCount}/${total} artifacts complete${skippedSuffix}`);
104
79
  console.log();
105
80
  for (const artifact of status.artifacts) {
106
81
  const indicator = getStatusIndicator(artifact.status);
107
82
  const color = getStatusColor(artifact.status);
108
83
  let line = `${indicator} ${artifact.id}`;
84
+ if (artifact.status === 'skipped') {
85
+ line += color(' (skipped: change declares skip_specs)');
86
+ }
109
87
  if (artifact.status === 'blocked' && artifact.missingDeps && artifact.missingDeps.length > 0) {
110
88
  line += color(` (blocked by: ${artifact.missingDeps.join(', ')})`);
111
89
  }
112
90
  console.log(line);
113
91
  }
114
- if (status.isComplete) {
92
+ if (status.isPlanningComplete) {
115
93
  console.log();
116
- console.log(chalk.green('All artifacts complete!'));
94
+ console.log(chalk.green('All planning artifacts complete!'));
117
95
  }
118
96
  }
119
97
  //# sourceMappingURL=status.js.map
@@ -39,11 +39,21 @@ export async function templatesCommand(options) {
39
39
  else {
40
40
  source = 'package';
41
41
  }
42
- const templates = graph.getAllArtifacts().map((artifact) => ({
43
- artifactId: artifact.id,
44
- templatePath: FileSystemUtils.canonicalizeExistingPath(path.join(schemaDir, 'templates', artifact.template)),
45
- source,
46
- }));
42
+ const templatesDir = path.join(schemaDir, 'templates');
43
+ const templates = graph.getAllArtifacts().map((artifact) => {
44
+ const templatePath = path.join(templatesDir, artifact.template);
45
+ try {
46
+ FileSystemUtils.assertPathWithin(templatesDir, templatePath);
47
+ return {
48
+ artifactId: artifact.id,
49
+ templatePath: FileSystemUtils.canonicalizeExistingPath(templatePath),
50
+ source,
51
+ };
52
+ }
53
+ catch {
54
+ throw new Error(`Template '${artifact.template}' for artifact '${artifact.id}' points outside the schema templates directory`);
55
+ }
56
+ });
47
57
  spinner?.stop();
48
58
  if (options.json) {
49
59
  const output = {};
@@ -1,4 +1,4 @@
1
- import type { spawn as nodeSpawn } from 'node:child_process';
1
+ import * as child_process from 'node:child_process';
2
2
  import { Command } from 'commander';
3
3
  import { type LaunchCommand } from '../core/openers.js';
4
4
  interface LaunchResult {
@@ -6,7 +6,7 @@ interface LaunchResult {
6
6
  signal: NodeJS.Signals | null;
7
7
  }
8
8
  export interface LaunchOptions {
9
- spawnFn?: typeof nodeSpawn;
9
+ spawnFn?: typeof child_process.spawn;
10
10
  }
11
11
  /**
12
12
  * Spawns the opener with this terminal's stdio. Resolves with the
@@ -8,18 +8,19 @@
8
8
  */
9
9
  import * as os from 'node:os';
10
10
  import { createRequire } from 'node:module';
11
- import { trackEvent, trackCommandFailed } from '../telemetry/index.js';
11
+ import { asErrorMessage, emitFailure, printJson } from './shared-output.js';
12
12
  import { Option } from 'commander';
13
+ import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
13
14
  import { buildWorksetCodeWorkspaceJson, getWorkset, getWorksetCodeWorkspacePath, listWorksets, readWorksetsState, removeWorkset, updateWorksetsState, validateWorksetName, withWorkset, withWorksetsLock, worksetNotFoundError, } from '../core/worksets.js';
15
+ import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
16
+ import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
14
17
  import { buildLaunchCommand, findOpener, isOpenerCommandAvailable, isOpenerEnabled, listOpenerChoices, mergeOpenerTable, } from '../core/openers.js';
15
18
  import { pathIsDirectory, writeFileAtomically } from '../core/file-state.js';
16
19
  import { getGlobalConfig, getGlobalConfigPath, } from '../core/global-config.js';
17
20
  import { StoreError } from '../core/store/errors.js';
18
21
  import { isInteractive } from '../utils/interactive.js';
19
- import { asErrorMessage, emitFailure, isPromptCancellationError, printJson, } from './shared-output.js';
20
- import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
21
- import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
22
- import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
22
+ import { isPromptCancellationError, } from './shared-output.js';
23
+ import { trackWorksetOperation } from '../telemetry/index.js';
23
24
  // cross-spawn is CJS with no types and only `workset open` needs it -
24
25
  // loaded lazily so every other CLI invocation skips its module graph.
25
26
  let cachedSpawn;
@@ -108,16 +109,14 @@ class WorksetCommand {
108
109
  workset = await this.composeFromFlags(name, options);
109
110
  }
110
111
  await updateWorksetsState((state) => withWorkset(state, workset));
111
- trackEvent('workset_created', {
112
- member_count: workset.members.length,
113
- has_tool: Boolean(workset.tool),
114
- });
115
112
  if (options.json) {
116
113
  printJson({ workset, status: [] });
117
114
  return;
118
115
  }
119
116
  console.log('');
120
117
  console.log(`Saved workset '${workset.name}' (${workset.members.length} member${workset.members.length === 1 ? '' : 's'}) to your machine.`);
118
+ // Track workset create operation
119
+ await trackWorksetOperation('create', workset.name);
121
120
  if (interactive && workset.tool !== undefined && table !== undefined) {
122
121
  const label = findOpener(table, workset.tool)?.label ?? workset.tool;
123
122
  let openNow = false;
@@ -179,22 +178,23 @@ class WorksetCommand {
179
178
  return;
180
179
  }
181
180
  if (worksets.length === 0) {
182
- console.log('No worksets saved. Create one with: openspec workset create');
181
+ console.log('No worksets saved.');
182
+ console.log('');
183
+ console.log('Next:');
184
+ console.log(' openspec workset create <name> --member <path>');
183
185
  return;
184
186
  }
185
- // The table is consulted only to render tool labels.
186
187
  const table = worksets.some((workset) => workset.tool !== undefined)
187
188
  ? readOpenerTable()
188
189
  : [];
189
190
  for (const workset of worksets) {
190
- const toolLabel = workset.tool !== undefined
191
- ? ` (opens in ${findOpener(table, workset.tool)?.label ?? workset.tool})`
192
- : '';
193
- console.log(`${workset.name}${toolLabel}`);
191
+ console.log(`${workset.name}${workset.tool !== undefined ? ` (opens in ${findOpener(table, workset.tool)?.label ?? workset.tool})` : ''}`);
194
192
  for (const row of formatMemberRows(workset.members)) {
195
193
  console.log(` ${row}`);
196
194
  }
197
195
  }
196
+ // Track workset list operation
197
+ await trackWorksetOperation('list');
198
198
  }
199
199
  catch (error) {
200
200
  emitFailure(options.json, { worksets: [], status: [] }, error, 'workset_error');
@@ -285,11 +285,8 @@ class WorksetCommand {
285
285
  else {
286
286
  console.log(`Handing this terminal to ${opener.label} for '${name}' (the session ends when you exit).`);
287
287
  }
288
- trackEvent('workset_opened', {
289
- opener_style: opener.style,
290
- member_count: prepared.surviving.length,
291
- skipped_count: prepared.skipped.length,
292
- });
288
+ // Track workset open operation
289
+ await trackWorksetOperation('open', name);
293
290
  let result;
294
291
  try {
295
292
  result = await launchOpenerCommand(launch);
@@ -357,15 +354,15 @@ class WorksetCommand {
357
354
  }
358
355
  }
359
356
  await removeWorkset(name);
360
- trackEvent('workset_removed');
361
357
  if (options.json) {
362
358
  printJson({ removed: { name }, status: [] });
363
359
  return;
364
360
  }
365
361
  console.log(`Removed workset '${name}'. Member folders were not touched.`);
362
+ // Track workset remove operation
363
+ await trackWorksetOperation('remove', name);
366
364
  }
367
365
  catch (error) {
368
- await trackCommandFailed('workset_remove', error);
369
366
  emitFailure(options.json, { removed: null, status: [] }, error, 'workset_error');
370
367
  }
371
368
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Fingerprint utilities for artifact content
3
+ *
4
+ * This module provides fingerprinting functionality for detecting changes in artifact
5
+ * content. It reuses fingerprint logic from src/core/archive.ts and is used to
6
+ * invalidate quiz passes when artifacts are modified.
7
+ *
8
+ * @module comprehension-quiz/fingerprint
9
+ */
10
+ /**
11
+ * Generate SHA-256 fingerprint of a file
12
+ *
13
+ * @param filePath - Path to the file to fingerprint
14
+ * @returns SHA-256 hash of file content
15
+ */
16
+ export declare function fingerprintFile(filePath: string): Promise<string>;
17
+ /**
18
+ * Generate SHA-256 fingerprint of a directory recursively
19
+ *
20
+ * @param dirPath - Path to the directory to fingerprint
21
+ * @returns SHA-256 hash of all file contents combined
22
+ */
23
+ export declare function fingerprintDirectory(dirPath: string): Promise<string>;
24
+ /**
25
+ * Generate combined fingerprint of multiple artifact files
26
+ *
27
+ * @param filePaths - Array of file paths to fingerprint
28
+ * @returns Combined SHA-256 hash of all file contents
29
+ */
30
+ export declare function fingerprintArtifacts(filePaths: string[]): Promise<string>;
31
+ /**
32
+ * Check if a pass record is valid for current artifact fingerprint
33
+ *
34
+ * @param changeName - Name of the change
35
+ * @param currentFingerprint - Current fingerprint of artifacts
36
+ * @returns True if pass record exists and fingerprint matches
37
+ */
38
+ export declare function isPassRecordValid(changeName: string, currentFingerprint: string): Promise<boolean>;
39
+ //# sourceMappingURL=fingerprint.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Fingerprint utilities for artifact content
3
+ *
4
+ * This module provides fingerprinting functionality for detecting changes in artifact
5
+ * content. It reuses fingerprint logic from src/core/archive.ts and is used to
6
+ * invalidate quiz passes when artifacts are modified.
7
+ *
8
+ * @module comprehension-quiz/fingerprint
9
+ */
10
+ import { createHash } from 'crypto';
11
+ import { promises as fs } from 'fs';
12
+ import path from 'path';
13
+ /**
14
+ * Generate SHA-256 fingerprint of a file
15
+ *
16
+ * @param filePath - Path to the file to fingerprint
17
+ * @returns SHA-256 hash of file content
18
+ */
19
+ export async function fingerprintFile(filePath) {
20
+ const fileHash = createHash('sha256');
21
+ const content = await fs.readFile(filePath);
22
+ fileHash.update(content);
23
+ return fileHash.digest('hex');
24
+ }
25
+ /**
26
+ * Generate SHA-256 fingerprint of a directory recursively
27
+ *
28
+ * @param dirPath - Path to the directory to fingerprint
29
+ * @returns SHA-256 hash of all file contents combined
30
+ */
31
+ export async function fingerprintDirectory(dirPath) {
32
+ const hash = createHash('sha256');
33
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
34
+ for (const entry of entries) {
35
+ const fullPath = path.join(dirPath, entry.name);
36
+ if (entry.isDirectory()) {
37
+ // Recursively fingerprint subdirectories
38
+ const dirHash = await fingerprintDirectory(fullPath);
39
+ hash.update(dirHash);
40
+ }
41
+ else if (entry.isFile()) {
42
+ // Fingerprint file content
43
+ const fileHash = await fingerprintFile(fullPath);
44
+ hash.update(fileHash);
45
+ }
46
+ }
47
+ return hash.digest('hex');
48
+ }
49
+ /**
50
+ * Generate combined fingerprint of multiple artifact files
51
+ *
52
+ * @param filePaths - Array of file paths to fingerprint
53
+ * @returns Combined SHA-256 hash of all file contents
54
+ */
55
+ export async function fingerprintArtifacts(filePaths) {
56
+ const hash = createHash('sha256');
57
+ for (const filePath of filePaths) {
58
+ try {
59
+ const fileHash = await fingerprintFile(filePath);
60
+ hash.update(fileHash);
61
+ }
62
+ catch (error) {
63
+ // Skip files that don't exist or can't be read
64
+ console.warn(`Warning: Could not fingerprint ${filePath}: ${error}`);
65
+ }
66
+ }
67
+ return hash.digest('hex');
68
+ }
69
+ /**
70
+ * Check if a pass record is valid for current artifact fingerprint
71
+ *
72
+ * @param changeName - Name of the change
73
+ * @param currentFingerprint - Current fingerprint of artifacts
74
+ * @returns True if pass record exists and fingerprint matches
75
+ */
76
+ export async function isPassRecordValid(changeName, currentFingerprint) {
77
+ const { getPassRecord } = await import('./pass-record.js');
78
+ const record = await getPassRecord(changeName);
79
+ if (!record) {
80
+ return false;
81
+ }
82
+ return record.fingerprint === currentFingerprint;
83
+ }
84
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Comprehension quiz system main entry point
3
+ *
4
+ * This module provides the main entry point for the comprehension quiz system,
5
+ * which enforces that developers read and understand change artifacts before
6
+ * implementation. The quiz blocks the `/opsx:apply` command until passed with a score
7
+ * of 80% or higher.
8
+ *
9
+ * @module comprehension-quiz
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { QuizExecutor } from './comprehension-quiz/quiz-executor.js';
14
+ * import { QuizQuestionStream, getQuestionCount } from './comprehension-quiz/question-generator.js';
15
+ *
16
+ * // Create on-demand stream — NO questions generated yet
17
+ * const totalQuestions = getQuestionCount(totalArtifactBytes);
18
+ * const stream = await QuizQuestionStream.create(artifactPaths, totalQuestions);
19
+ *
20
+ * // Execute quiz — one question fetched per answer
21
+ * const executor = new QuizExecutor();
22
+ * const result = await executor.executeQuiz(stream, changeName);
23
+ *
24
+ * if (result.passed) {
25
+ * console.log('Quiz passed! Proceeding with implementation.');
26
+ * } else {
27
+ * console.log('Quiz failed. Please review the artifacts and try again.');
28
+ * }
29
+ * ```
30
+ */
31
+ export * from './types.js';
32
+ export * from './providers/interface.js';
33
+ export * from './providers/factory.js';
34
+ export { OpenAIProvider } from './providers/openai.js';
35
+ export { AnthropicProvider } from './providers/anthropic.js';
36
+ export { OllamaProvider } from './providers/ollama.js';
37
+ export { LocalProvider } from './providers/local.js';
38
+ export { fingerprintFile, fingerprintDirectory, fingerprintArtifacts, isPassRecordValid } from './fingerprint.js';
39
+ export * from './pass-record.js';
40
+ export { QuizExecutor } from './quiz-executor.js';
41
+ export { generateQuizQuestions, generateQuestionsForArtifact, getQuestionCount, calculateQuestionAllocation, QuizQuestionStream, } from './question-generator.js';
42
+ //# sourceMappingURL=index.d.ts.map