@codewalla_india/openspec 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +272 -191
  3. package/bin/openspec.js +5 -5
  4. package/dist/cli/index.js +99 -62
  5. package/dist/commands/change.d.ts +0 -2
  6. package/dist/commands/change.js +107 -86
  7. package/dist/commands/config.js +14 -16
  8. package/dist/commands/doctor.js +9 -4
  9. package/dist/commands/feedback.js +79 -30
  10. package/dist/commands/modify.d.ts +26 -0
  11. package/dist/commands/modify.js +147 -0
  12. package/dist/commands/schema.js +170 -91
  13. package/dist/commands/show.js +5 -2
  14. package/dist/commands/spec.js +49 -29
  15. package/dist/commands/store.js +15 -21
  16. package/dist/commands/validate.d.ts +8 -0
  17. package/dist/commands/validate.js +43 -20
  18. package/dist/commands/workflow/index.d.ts +2 -2
  19. package/dist/commands/workflow/index.js +1 -1
  20. package/dist/commands/workflow/instructions.d.ts +6 -26
  21. package/dist/commands/workflow/instructions.js +183 -376
  22. package/dist/commands/workflow/new-change.d.ts +0 -4
  23. package/dist/commands/workflow/new-change.js +10 -28
  24. package/dist/commands/workflow/shared.d.ts +34 -19
  25. package/dist/commands/workflow/shared.js +34 -4
  26. package/dist/commands/workflow/status.js +10 -32
  27. package/dist/commands/workflow/templates.js +15 -5
  28. package/dist/commands/workset.d.ts +2 -2
  29. package/dist/commands/workset.js +19 -22
  30. package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
  31. package/dist/comprehension-quiz/fingerprint.js +84 -0
  32. package/dist/comprehension-quiz/index.d.ts +42 -0
  33. package/dist/comprehension-quiz/index.js +47 -0
  34. package/dist/comprehension-quiz/pass-record.d.ts +44 -0
  35. package/dist/comprehension-quiz/pass-record.js +98 -0
  36. package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +53 -0
  38. package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
  39. package/dist/comprehension-quiz/providers/factory.js +174 -0
  40. package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
  41. package/dist/comprehension-quiz/providers/interface.js +63 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +34 -0
  43. package/dist/comprehension-quiz/providers/local.js +53 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +53 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
  47. package/dist/comprehension-quiz/providers/openai.js +53 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +69 -0
  49. package/dist/comprehension-quiz/question-generator.js +141 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +165 -0
  52. package/dist/comprehension-quiz/types.d.ts +115 -0
  53. package/dist/comprehension-quiz/types.js +11 -0
  54. package/dist/core/archive.d.ts +14 -1
  55. package/dist/core/archive.js +1350 -190
  56. package/dist/core/artifact-graph/graph.d.ts +28 -9
  57. package/dist/core/artifact-graph/graph.js +94 -43
  58. package/dist/core/artifact-graph/index.d.ts +1 -1
  59. package/dist/core/artifact-graph/index.js +1 -1
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
  61. package/dist/core/artifact-graph/instruction-loader.js +70 -12
  62. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  63. package/dist/core/artifact-graph/outputs.js +67 -5
  64. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  65. package/dist/core/artifact-graph/resolver.js +69 -15
  66. package/dist/core/artifact-graph/types.js +21 -3
  67. package/dist/core/available-tools.d.ts +2 -2
  68. package/dist/core/available-tools.js +14 -3
  69. package/dist/core/change-metadata/schema.d.ts +2 -0
  70. package/dist/core/change-metadata/schema.js +13 -0
  71. package/dist/core/change-status-policy.d.ts +10 -1
  72. package/dist/core/change-status-policy.js +30 -2
  73. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  74. package/dist/core/command-generation/adapters/amazon-q.js +11 -5
  75. package/dist/core/command-generation/adapters/antigravity.js +6 -5
  76. package/dist/core/command-generation/adapters/auggie.js +7 -6
  77. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  78. package/dist/core/command-generation/adapters/bob.js +11 -10
  79. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  80. package/dist/core/command-generation/adapters/claude.js +12 -17
  81. package/dist/core/command-generation/adapters/cline.js +5 -5
  82. package/dist/core/command-generation/adapters/codebuddy.js +8 -7
  83. package/dist/core/command-generation/adapters/continue.js +8 -7
  84. package/dist/core/command-generation/adapters/costrict.js +7 -6
  85. package/dist/core/command-generation/adapters/crush.js +9 -9
  86. package/dist/core/command-generation/adapters/cursor.js +8 -8
  87. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  88. package/dist/core/command-generation/adapters/devin.js +36 -0
  89. package/dist/core/command-generation/adapters/factory.js +7 -6
  90. package/dist/core/command-generation/adapters/gemini.js +40 -5
  91. package/dist/core/command-generation/adapters/github-copilot.js +6 -5
  92. package/dist/core/command-generation/adapters/iflow.js +9 -8
  93. package/dist/core/command-generation/adapters/index.d.ts +4 -2
  94. package/dist/core/command-generation/adapters/index.js +4 -2
  95. package/dist/core/command-generation/adapters/junie.js +6 -5
  96. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  97. package/dist/core/command-generation/adapters/kiro.js +6 -5
  98. package/dist/core/command-generation/adapters/lingma.js +9 -9
  99. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  101. package/dist/core/command-generation/adapters/opencode.js +6 -8
  102. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  103. package/dist/core/command-generation/adapters/pi.js +7 -10
  104. package/dist/core/command-generation/adapters/qoder.js +9 -9
  105. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  106. package/dist/core/command-generation/adapters/qwen.js +14 -9
  107. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  108. package/dist/core/command-generation/adapters/roocode.js +9 -9
  109. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  110. package/dist/core/command-generation/adapters/trae.js +28 -0
  111. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  112. package/dist/core/command-generation/adapters/zcode.js +33 -0
  113. package/dist/core/command-generation/generator.d.ts +8 -0
  114. package/dist/core/command-generation/generator.js +15 -1
  115. package/dist/core/command-generation/index.d.ts +1 -1
  116. package/dist/core/command-generation/index.js +1 -1
  117. package/dist/core/command-generation/invocation.d.ts +73 -0
  118. package/dist/core/command-generation/invocation.js +77 -0
  119. package/dist/core/command-generation/registry.js +8 -4
  120. package/dist/core/command-generation/types.d.ts +9 -2
  121. package/dist/core/command-generation/yaml.d.ts +20 -6
  122. package/dist/core/command-generation/yaml.js +31 -21
  123. package/dist/core/command-surface.d.ts +15 -0
  124. package/dist/core/command-surface.js +33 -0
  125. package/dist/core/completions/command-registry.js +16 -61
  126. package/dist/core/completions/generators/bash-generator.js +41 -41
  127. package/dist/core/completions/generators/fish-generator.js +7 -7
  128. package/dist/core/completions/generators/powershell-generator.js +44 -37
  129. package/dist/core/completions/generators/zsh-generator.js +33 -33
  130. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  131. package/dist/core/completions/installers/zsh-installer.js +20 -4
  132. package/dist/core/completions/templates/bash-templates.js +24 -24
  133. package/dist/core/completions/templates/fish-templates.js +38 -38
  134. package/dist/core/completions/templates/powershell-templates.js +28 -28
  135. package/dist/core/completions/templates/zsh-templates.js +39 -39
  136. package/dist/core/config-prompts.d.ts +1 -1
  137. package/dist/core/config-prompts.js +14 -1
  138. package/dist/core/config-schema.d.ts +10 -1
  139. package/dist/core/config-schema.js +67 -1
  140. package/dist/core/config.d.ts +15 -0
  141. package/dist/core/config.js +46 -5
  142. package/dist/core/file-state.js +56 -24
  143. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  144. package/dist/core/github-copilot/cloud-agent.js +497 -0
  145. package/dist/core/global-config.d.ts +16 -0
  146. package/dist/core/init.d.ts +58 -0
  147. package/dist/core/init.js +474 -73
  148. package/dist/core/legacy-cleanup.d.ts +59 -0
  149. package/dist/core/legacy-cleanup.js +253 -24
  150. package/dist/core/list.js +23 -16
  151. package/dist/core/migration.d.ts +73 -1
  152. package/dist/core/migration.js +360 -11
  153. package/dist/core/modify/conflict-detector.d.ts +26 -0
  154. package/dist/core/modify/conflict-detector.js +62 -0
  155. package/dist/core/modify/dependency-propagator.d.ts +38 -0
  156. package/dist/core/modify/dependency-propagator.js +103 -0
  157. package/dist/core/modify/history-tracker.d.ts +34 -0
  158. package/dist/core/modify/history-tracker.js +114 -0
  159. package/dist/core/modify/index.d.ts +7 -0
  160. package/dist/core/modify/index.js +7 -0
  161. package/dist/core/onboarding-commands.d.ts +30 -0
  162. package/dist/core/onboarding-commands.js +38 -0
  163. package/dist/core/openspec-root.js +18 -15
  164. package/dist/core/parsers/change-parser.d.ts +17 -2
  165. package/dist/core/parsers/change-parser.js +35 -21
  166. package/dist/core/parsers/code-fence.d.ts +15 -0
  167. package/dist/core/parsers/code-fence.js +49 -0
  168. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  169. package/dist/core/parsers/markdown-parser.js +7 -60
  170. package/dist/core/parsers/requirement-blocks.d.ts +29 -0
  171. package/dist/core/parsers/requirement-blocks.js +150 -31
  172. package/dist/core/parsers/requirement-text.d.ts +48 -0
  173. package/dist/core/parsers/requirement-text.js +98 -0
  174. package/dist/core/parsers/spec-structure.d.ts +1 -1
  175. package/dist/core/parsers/spec-structure.js +20 -30
  176. package/dist/core/profile-sync-drift.d.ts +0 -8
  177. package/dist/core/profile-sync-drift.js +49 -50
  178. package/dist/core/profiles.d.ts +2 -2
  179. package/dist/core/profiles.js +2 -2
  180. package/dist/core/project-config.d.ts +27 -18
  181. package/dist/core/project-config.js +101 -34
  182. package/dist/core/references.js +42 -4
  183. package/dist/core/relationship-health.d.ts +8 -0
  184. package/dist/core/relationship-health.js +13 -0
  185. package/dist/core/root-selection.d.ts +9 -6
  186. package/dist/core/root-selection.js +42 -5
  187. package/dist/core/schemas/base.schema.js +7 -2
  188. package/dist/core/shared/allowed-tools.d.ts +12 -0
  189. package/dist/core/shared/allowed-tools.js +12 -0
  190. package/dist/core/shared/index.d.ts +1 -0
  191. package/dist/core/shared/index.js +1 -0
  192. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  193. package/dist/core/shared/skill-content-equivalence.js +41 -0
  194. package/dist/core/shared/skill-generation.js +17 -13
  195. package/dist/core/shared/skill-paths.d.ts +13 -0
  196. package/dist/core/shared/skill-paths.js +23 -0
  197. package/dist/core/shared/tool-detection.d.ts +27 -7
  198. package/dist/core/shared/tool-detection.js +167 -37
  199. package/dist/core/shared-skill-target.d.ts +16 -0
  200. package/dist/core/shared-skill-target.js +156 -0
  201. package/dist/core/specs-apply.d.ts +80 -33
  202. package/dist/core/specs-apply.js +667 -145
  203. package/dist/core/store/git.d.ts +13 -0
  204. package/dist/core/store/git.js +24 -0
  205. package/dist/core/store/operations.js +20 -0
  206. package/dist/core/templates/skill-templates.d.ts +3 -2
  207. package/dist/core/templates/skill-templates.js +3 -2
  208. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  209. package/dist/core/templates/workflows/apply-change.js +186 -224
  210. package/dist/core/templates/workflows/archive-change.js +387 -260
  211. package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
  212. package/dist/core/templates/workflows/continue-change.js +210 -219
  213. package/dist/core/templates/workflows/explore.js +479 -443
  214. package/dist/core/templates/workflows/feedback.js +97 -97
  215. package/dist/core/templates/workflows/ff-change.js +200 -191
  216. package/dist/core/templates/workflows/modify-change.d.ts +4 -1
  217. package/dist/core/templates/workflows/modify-change.js +237 -115
  218. package/dist/core/templates/workflows/new-change.js +127 -134
  219. package/dist/core/templates/workflows/onboard.js +548 -542
  220. package/dist/core/templates/workflows/propose.js +273 -217
  221. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  222. package/dist/core/templates/workflows/store-selection.js +1 -1
  223. package/dist/core/templates/workflows/sync-specs.js +500 -273
  224. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  225. package/dist/core/templates/workflows/update-change.js +177 -0
  226. package/dist/core/templates/workflows/verify-change.js +324 -326
  227. package/dist/core/update.d.ts +40 -5
  228. package/dist/core/update.js +411 -96
  229. package/dist/core/validation/constants.d.ts +4 -1
  230. package/dist/core/validation/constants.js +4 -1
  231. package/dist/core/validation/plan-validator.d.ts +8 -0
  232. package/dist/core/validation/plan-validator.js +45 -0
  233. package/dist/core/validation/task-numbering.d.ts +16 -0
  234. package/dist/core/validation/task-numbering.js +57 -0
  235. package/dist/core/validation/validator.d.ts +27 -3
  236. package/dist/core/validation/validator.js +337 -48
  237. package/dist/core/version-check.d.ts +152 -0
  238. package/dist/core/version-check.js +731 -0
  239. package/dist/core/view.js +13 -18
  240. package/dist/prompts/searchable-multi-select.js +1 -1
  241. package/dist/telemetry/caller-detection.d.ts +27 -0
  242. package/dist/telemetry/caller-detection.js +85 -0
  243. package/dist/telemetry/caller-detection.test.d.ts +5 -0
  244. package/dist/telemetry/config.d.ts +48 -2
  245. package/dist/telemetry/config.js +113 -11
  246. package/dist/telemetry/config.test.d.ts +5 -0
  247. package/dist/telemetry/deduplication.d.ts +35 -0
  248. package/dist/telemetry/deduplication.js +82 -0
  249. package/dist/telemetry/deduplication.test.d.ts +5 -0
  250. package/dist/telemetry/identity.d.ts +62 -22
  251. package/dist/telemetry/identity.js +147 -102
  252. package/dist/telemetry/index.d.ts +126 -15
  253. package/dist/telemetry/index.js +569 -37
  254. package/dist/telemetry/sanitization.d.ts +40 -0
  255. package/dist/telemetry/sanitization.js +114 -0
  256. package/dist/telemetry/sanitization.test.d.ts +5 -0
  257. package/dist/telemetry/throttling.d.ts +20 -0
  258. package/dist/telemetry/throttling.js +70 -0
  259. package/dist/ui/welcome-screen.d.ts +9 -1
  260. package/dist/ui/welcome-screen.js +91 -38
  261. package/dist/utils/change-metadata.d.ts +43 -0
  262. package/dist/utils/change-metadata.js +123 -7
  263. package/dist/utils/change-utils.d.ts +10 -4
  264. package/dist/utils/change-utils.js +20 -13
  265. package/dist/utils/ci.d.ts +12 -0
  266. package/dist/utils/ci.js +16 -0
  267. package/dist/utils/command-references.d.ts +92 -6
  268. package/dist/utils/command-references.js +180 -7
  269. package/dist/utils/date.d.ts +8 -0
  270. package/dist/utils/date.js +13 -0
  271. package/dist/utils/file-system.d.ts +10 -0
  272. package/dist/utils/file-system.js +70 -0
  273. package/dist/utils/index.d.ts +1 -1
  274. package/dist/utils/index.js +1 -1
  275. package/dist/utils/interactive.d.ts +23 -0
  276. package/dist/utils/interactive.js +32 -0
  277. package/dist/utils/item-discovery.d.ts +19 -0
  278. package/dist/utils/item-discovery.js +30 -48
  279. package/dist/utils/shell-detection.d.ts +1 -1
  280. package/dist/utils/shell-detection.js +64 -11
  281. package/dist/utils/spec-discovery.d.ts +40 -0
  282. package/dist/utils/spec-discovery.js +115 -0
  283. package/dist/utils/task-progress.d.ts +29 -1
  284. package/dist/utils/task-progress.js +109 -15
  285. package/package.json +96 -84
  286. package/schemas/spec-driven/schema.yaml +252 -184
  287. package/schemas/spec-driven/templates/design.md +19 -19
  288. package/schemas/spec-driven/templates/plan-template.md +91 -0
  289. package/schemas/spec-driven/templates/proposal.md +29 -24
  290. package/schemas/spec-driven/templates/spec.md +11 -8
  291. package/schemas/spec-driven/templates/tasks.md +9 -9
  292. package/scripts/postinstall.js +83 -83
  293. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  294. package/dist/core/command-generation/adapters/codex.js +0 -39
  295. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  296. package/dist/core/command-generation/adapters/windsurf.js +0 -38
  297. package/dist/core/comprehension/config.d.ts +0 -20
  298. package/dist/core/comprehension/config.js +0 -23
  299. package/dist/core/comprehension/fingerprint.d.ts +0 -14
  300. package/dist/core/comprehension/fingerprint.js +0 -33
  301. package/dist/core/comprehension/index.d.ts +0 -57
  302. package/dist/core/comprehension/index.js +0 -87
  303. package/dist/core/comprehension/pass-record.d.ts +0 -29
  304. package/dist/core/comprehension/pass-record.js +0 -64
  305. package/dist/core/comprehension/stats.d.ts +0 -35
  306. package/dist/core/comprehension/stats.js +0 -133
  307. package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
  308. package/dist/core/templates/workflows/comprehension-guidance.js +0 -73
  309. package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
  310. package/dist/core/templates/workflows/mcp-guidance.js +0 -131
  311. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
  312. package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
  313. package/dist/telemetry/caller.d.ts +0 -5
  314. package/dist/telemetry/caller.js +0 -29
  315. package/dist/telemetry/client.d.ts +0 -27
  316. package/dist/telemetry/client.js +0 -127
  317. package/dist/telemetry/command-context.d.ts +0 -13
  318. package/dist/telemetry/command-context.js +0 -59
  319. package/dist/telemetry/comprehension.d.ts +0 -44
  320. package/dist/telemetry/comprehension.js +0 -105
  321. package/dist/telemetry/content.d.ts +0 -10
  322. package/dist/telemetry/content.js +0 -56
  323. package/dist/telemetry/git-stats.d.ts +0 -12
  324. package/dist/telemetry/git-stats.js +0 -69
  325. package/dist/telemetry/identify-cache.d.ts +0 -7
  326. package/dist/telemetry/identify-cache.js +0 -47
  327. package/dist/telemetry/input.d.ts +0 -17
  328. package/dist/telemetry/input.js +0 -68
  329. package/dist/telemetry/marker.d.ts +0 -37
  330. package/dist/telemetry/marker.js +0 -67
  331. package/dist/telemetry/workflow.d.ts +0 -85
  332. package/dist/telemetry/workflow.js +0 -318
  333. package/schemas/spec-driven/templates/plan.md +0 -19
@@ -7,31 +7,89 @@
7
7
  import ora from 'ora';
8
8
  import path from 'path';
9
9
  import * as fs from 'fs';
10
- import { loadChangeContext, generateInstructions, formatChangeStatus, resolveSchema, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
10
+ import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputPath, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
11
11
  import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
12
- import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput, isStoreSelectedRoot, } from '../../core/root-selection.js';
12
+ import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput, } from '../../core/root-selection.js';
13
13
  import { assembleReferenceIndex, renderReferencedStoresBlock, renderReferencedStoresSection, } from '../../core/references.js';
14
14
  import { readRegistrySnapshot } from '../../core/store/registry.js';
15
- import { readProjectConfig } from '../../core/project-config.js';
16
- import { checkComprehensionGate, ComprehensionPassError, computeSpecStats, recordComprehensionPass, resolveComprehensionConfig, } from '../../core/comprehension/index.js';
17
- import { maybeEmitProposalReady, maybeEmitApplyReady, trackArtifactInstructions, trackArtifactContentChanges, trackArtifactModifyRequested, incrementComprehensionAttempt, incrementComprehensionFailureCount, trackComprehensionAttempt, trackComprehensionGateChecked, trackComprehensionRetakeRequired, } from '../../telemetry/index.js';
18
- import { normalizeEditor, resolveWorkflowInputAsync, } from '../../telemetry/input.js';
15
+ import { loadOperationInputs, readProjectConfig, } from '../../core/project-config.js';
19
16
  import { validateChangeExists, validateSchemaExists, } from './shared.js';
20
- function buildArtifactPresence(contextFiles, pendingTaskCount) {
21
- return {
22
- hasPlan: (contextFiles.plan?.length ?? 0) > 0,
23
- hasProposal: (contextFiles.proposal?.length ?? 0) > 0,
24
- hasDesign: (contextFiles.design?.length ?? 0) > 0,
25
- hasSpecs: (contextFiles.specs?.length ?? 0) > 0,
26
- hasTasks: pendingTaskCount > 0 || (contextFiles.tasks?.length ?? 0) > 0,
17
+ import { parseTaskLines } from '../../utils/task-progress.js';
18
+ import { fingerprintArtifacts, isPassRecordValid } from '../../comprehension-quiz/fingerprint.js';
19
+ // -----------------------------------------------------------------------------
20
+ // Comprehension Quiz Check
21
+ // -----------------------------------------------------------------------------
22
+ /**
23
+ * Check comprehension quiz status for a change
24
+ * @param changeName - Name of the change
25
+ * @param changeDir - Directory of the change
26
+ * @param contextFiles - Context files mapping
27
+ * @returns Comprehension status object
28
+ */
29
+ async function checkComprehensionStatus(changeName, changeDir, contextFiles) {
30
+ // Get all artifact file paths
31
+ const artifactPaths = [];
32
+ for (const files of Object.values(contextFiles)) {
33
+ artifactPaths.push(...files);
34
+ }
35
+ // Generate current fingerprint
36
+ const currentFingerprint = await fingerprintArtifacts(artifactPaths);
37
+ // Check if pass record is valid
38
+ const passValid = await isPassRecordValid(changeName, currentFingerprint);
39
+ // Calculate question allocation based on spec-driven schema defaults
40
+ const totalSize = artifactPaths.reduce((sum, filePath) => {
41
+ try {
42
+ return sum + fs.statSync(filePath).size;
43
+ }
44
+ catch {
45
+ return sum;
46
+ }
47
+ }, 0);
48
+ const questionCount = totalSize < 2048 ? 5 : totalSize < 10240 ? 7 : 10;
49
+ const questionAllocation = {
50
+ plan: Math.round(questionCount * 0.5),
51
+ specs: Math.round(questionCount * 0.2),
52
+ design: Math.round(questionCount * 0.1),
53
+ proposal: Math.round(questionCount * 0.1),
54
+ tasks: Math.round(questionCount * 0.1),
27
55
  };
28
- }
29
- function resolvePlanPath(contextFiles, changeDir) {
30
- if (contextFiles.plan?.[0]) {
31
- return contextFiles.plan[0];
56
+ // Count requirements and scenarios from specs
57
+ const specFiles = contextFiles.specs || [];
58
+ let requirementCount = 0;
59
+ let scenarioCount = 0;
60
+ for (const specFile of specFiles) {
61
+ try {
62
+ const content = await fs.promises.readFile(specFile, 'utf-8');
63
+ requirementCount += (content.match(/### Requirement:/g) || []).length;
64
+ scenarioCount += (content.match(/#### Scenario:/g) || []).length;
65
+ }
66
+ catch {
67
+ // Skip files that can't be read
68
+ }
69
+ }
70
+ // Count pending tasks
71
+ const tasksFile = contextFiles.tasks?.[0];
72
+ let pendingTaskCount = 0;
73
+ if (tasksFile) {
74
+ try {
75
+ const content = await fs.promises.readFile(tasksFile, 'utf-8');
76
+ pendingTaskCount = (content.match(/- \[ \]/g) || []).length;
77
+ }
78
+ catch {
79
+ // Skip if can't read
80
+ }
32
81
  }
33
- const fallback = path.join(changeDir, 'plan.md');
34
- return fs.existsSync(fallback) ? fallback : null;
82
+ return {
83
+ required: true,
84
+ passed: passValid,
85
+ thresholdPercent: 80,
86
+ questionCount,
87
+ questionAllocation,
88
+ optionsPerQuestion: 3,
89
+ requirementCount,
90
+ scenarioCount,
91
+ pendingTaskCount,
92
+ };
35
93
  }
36
94
  // -----------------------------------------------------------------------------
37
95
  // Artifact Instructions Command
@@ -74,10 +132,12 @@ export async function instructionsCommand(artifactId, options) {
74
132
  if (options.schema) {
75
133
  validateSchemaExists(options.schema, projectRoot);
76
134
  }
135
+ const { projectConfig, references } = await loadRootConfigContext(root);
77
136
  // loadChangeContext will auto-detect schema from metadata if not provided
78
137
  const context = loadChangeContext(projectRoot, changeName, options.schema, {
79
138
  changeDir: getChangeDir(planningHome, changeName),
80
139
  planningHome,
140
+ projectConfig,
81
141
  });
82
142
  if (!artifactId) {
83
143
  spinner?.stop();
@@ -90,32 +150,11 @@ export async function instructionsCommand(artifactId, options) {
90
150
  const validIds = context.graph.getAllArtifacts().map((a) => a.id);
91
151
  throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
92
152
  }
93
- const { projectConfig, references } = await loadRootConfigContext(root);
94
153
  const instructions = generateInstructions(context, artifactId, projectRoot, {
95
154
  projectConfig,
96
155
  references,
97
156
  });
98
157
  const isBlocked = instructions.dependencies.some((d) => !d.done);
99
- const artifactOutputs = resolveArtifactOutputs(context.changeDir, artifact.generates);
100
- await trackArtifactInstructions({
101
- changeDir: context.changeDir,
102
- changeName,
103
- artifactId,
104
- artifactWasDone: artifactOutputs.length > 0,
105
- artifactPaths: artifactOutputs,
106
- });
107
- const contextFiles = {};
108
- for (const a of context.graph.getAllArtifacts()) {
109
- const outputs = resolveArtifactOutputs(context.changeDir, a.generates);
110
- if (outputs.length > 0) {
111
- contextFiles[a.id] = outputs;
112
- }
113
- }
114
- await trackArtifactContentChanges({
115
- changeDir: context.changeDir,
116
- changeName,
117
- contextFiles,
118
- });
119
158
  spinner?.stop();
120
159
  if (options.json) {
121
160
  console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
@@ -133,6 +172,17 @@ export function printInstructionsText(instructions, isBlocked) {
133
172
  // Opening tag
134
173
  console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
135
174
  console.log();
175
+ // Artifacts skipped via skip_specs get no creation directive: emitting the
176
+ // task/template anyway would prompt an agent to write spec files that
177
+ // validate then rejects as conflicting with the marker.
178
+ if (instructions.skipped) {
179
+ console.log('<warning>');
180
+ console.log(instructions.warning ?? 'This artifact is skipped (skip_specs is set in .openspec.yaml).');
181
+ console.log('</warning>');
182
+ console.log();
183
+ console.log('</artifact>');
184
+ return;
185
+ }
136
186
  // Warning for blocked artifacts
137
187
  if (isBlocked) {
138
188
  const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
@@ -174,9 +224,18 @@ export function printInstructionsText(instructions, isBlocked) {
174
224
  // Dependencies (files to read for context)
175
225
  if (dependencies.length > 0) {
176
226
  console.log('<dependencies>');
177
- console.log('Read these files for context before creating this artifact:');
227
+ console.log('Read the current contents of these files before creating this artifact (re-read them from disk even if you saw them earlier - they may have been edited):');
178
228
  console.log();
179
229
  for (const dep of dependencies) {
230
+ // A dependency satisfied via skip_specs has no files by design: telling
231
+ // the agent to read them (or calling them "done") would send it hunting
232
+ // for spec files that must not exist.
233
+ if (dep.skipped) {
234
+ console.log(`<dependency id="${dep.id}" status="skipped">`);
235
+ console.log(` <description>Skipped: the change declares skip_specs, so this artifact has no files to read.</description>`);
236
+ console.log('</dependency>');
237
+ continue;
238
+ }
180
239
  const status = dep.done ? 'done' : 'missing';
181
240
  const fullPath = path.join(changeDir, dep.path);
182
241
  console.log(`<dependency id="${dep.id}" status="${status}">`);
@@ -224,49 +283,29 @@ export function printInstructionsText(instructions, isBlocked) {
224
283
  // Apply Instructions Command
225
284
  // -----------------------------------------------------------------------------
226
285
  /**
227
- * Parses tasks.md content and extracts task items with their completion status.
286
+ * Turns parsed task lines into the listed task items.
287
+ *
288
+ * A checkbox with no text after it is left out of the list: this is work for an
289
+ * agent to act on and tick off, and a bare `- [ ]` gives it nothing to match.
290
+ * It still counts toward progress, which is taken from every parsed line, so
291
+ * this list can be shorter than the totals beside it but never disagrees with
292
+ * `openspec list` or archive about how much work is left. An empty list is also
293
+ * what puts apply in its "nothing to work on" state, so a file of nothing but
294
+ * text-less checkboxes asks to be rewritten instead of being called done.
228
295
  */
229
- function parseTasksFile(content) {
296
+ function toTaskItems(parsed) {
230
297
  const tasks = [];
231
- const lines = content.split('\n');
232
- let taskIndex = 0;
233
- for (const line of lines) {
234
- // Match checkbox patterns: - [ ] or - [x] or - [X]
235
- const checkboxMatch = line.match(/^[-*]\s*\[([ xX])\]\s*(.+)\s*$/);
236
- if (checkboxMatch) {
237
- taskIndex++;
238
- const done = checkboxMatch[1].toLowerCase() === 'x';
239
- const description = checkboxMatch[2].trim();
240
- tasks.push({
241
- id: `${taskIndex}`,
242
- description,
243
- done,
244
- });
245
- }
298
+ for (const task of parsed) {
299
+ if (task.description.length === 0)
300
+ continue;
301
+ tasks.push({
302
+ id: `${tasks.length + 1}`,
303
+ description: task.description,
304
+ done: task.done,
305
+ });
246
306
  }
247
307
  return tasks;
248
308
  }
249
- function resolveComprehensionQuestionCount(optionCount, specPaths, projectConfig, pendingTaskCount, artifactPresence) {
250
- if (optionCount !== undefined && optionCount > 0) {
251
- return optionCount;
252
- }
253
- const config = resolveComprehensionConfig(projectConfig);
254
- return computeSpecStats(specPaths, config, pendingTaskCount, artifactPresence).questionCount;
255
- }
256
- async function emitComprehensionAttemptAfterPass(params) {
257
- await trackComprehensionAttempt({
258
- changeDir: params.changeDir,
259
- changeName: params.changeName,
260
- attempt: params.attempt,
261
- scorePercent: params.scorePercent,
262
- thresholdPercent: params.thresholdPercent,
263
- questionCount: params.questionCount,
264
- passed: true,
265
- failureCountBefore: params.failureCountBefore,
266
- nextMilestone: params.applyReadyEmitted ? 'apply_ready' : undefined,
267
- contextFiles: params.contextFiles,
268
- });
269
- }
270
309
  /**
271
310
  * Generates apply instructions for implementing tasks from a change.
272
311
  * Schema-aware: reads apply phase configuration from schema to determine
@@ -279,6 +318,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
279
318
  const context = loadChangeContext(projectRoot, changeName, schemaName, {
280
319
  changeDir: getChangeDir(planningHome, changeName),
281
320
  planningHome,
321
+ projectConfig: options.projectConfig,
282
322
  });
283
323
  const changeDir = context.changeDir;
284
324
  // Get the full schema to access the apply phase configuration
@@ -289,9 +329,15 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
289
329
  const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
290
330
  const tracksFile = applyConfig?.tracks ?? null;
291
331
  const schemaInstruction = applyConfig?.instruction ?? null;
292
- // Check which required artifacts are missing
332
+ const operationInputs = loadOperationInputs(options.projectConfig ?? null, 'apply');
333
+ // Check which required artifacts are missing. Artifacts the change skips
334
+ // via skip_specs count as present - their files must not exist, and
335
+ // status already reports them complete, so apply cannot block on them.
293
336
  const missingArtifacts = [];
294
337
  for (const artifactId of requiredArtifactIds) {
338
+ if (context.skippedArtifacts?.has(artifactId)) {
339
+ continue;
340
+ }
295
341
  const artifact = schema.artifacts.find((a) => a.id === artifactId);
296
342
  if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
297
343
  missingArtifacts.push(artifactId);
@@ -306,38 +352,47 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
306
352
  }
307
353
  }
308
354
  // Parse tasks if tracking file exists
309
- let tasks = [];
355
+ let parsedTasks = [];
310
356
  let tracksFileExists = false;
311
357
  if (tracksFile) {
312
- const tracksPath = path.join(changeDir, tracksFile);
358
+ const tracksPath = resolveArtifactOutputPath(changeDir, tracksFile);
313
359
  tracksFileExists = fs.existsSync(tracksPath);
314
360
  if (tracksFileExists) {
315
361
  const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
316
- tasks = parseTasksFile(tasksContent);
362
+ parsedTasks = parseTaskLines(tasksContent);
317
363
  }
318
364
  }
319
- // Calculate progress
320
- const total = tasks.length;
321
- const complete = tasks.filter((t) => t.done).length;
365
+ const tasks = toTaskItems(parsedTasks);
366
+ // Calculate progress over every checkbox in the file, listed or not, so these
367
+ // numbers match `openspec list` and archive's incomplete-task check.
368
+ const total = parsedTasks.length;
369
+ const complete = parsedTasks.filter((task) => task.done).length;
322
370
  const remaining = total - complete;
323
371
  // Determine state and instruction
324
372
  let state;
325
373
  let instruction;
374
+ // Check comprehension quiz status first
375
+ const comprehensionStatus = await checkComprehensionStatus(changeName, changeDir, contextFiles);
326
376
  if (missingArtifacts.length > 0) {
327
377
  state = 'blocked';
328
378
  instruction = `Cannot apply this change yet. Missing artifacts: ${missingArtifacts.join(', ')}.\nUse the openspec-continue-change skill to create the missing artifacts first.`;
329
379
  }
380
+ else if (comprehensionStatus.required && !comprehensionStatus.passed) {
381
+ state = 'comprehension_blocked';
382
+ instruction = 'Complete the comprehension quiz in /opsx:apply before implementation (score ≥ 80% on proposal, design, specs, plan, and tasks; plan receives the majority of questions per questionAllocation).';
383
+ }
330
384
  else if (tracksFile && !tracksFileExists) {
331
385
  // Tracking file configured but doesn't exist yet
332
386
  const tracksFilename = path.basename(tracksFile);
333
387
  state = 'blocked';
334
388
  instruction = `The ${tracksFilename} file is missing and must be created.\nUse openspec-continue-change to generate the tracking file.`;
335
389
  }
336
- else if (tracksFile && tracksFileExists && total === 0) {
337
- // Tracking file exists but contains no tasks
390
+ else if (tracksFile && tracksFileExists && tasks.length === 0) {
391
+ // Tracking file exists but lists nothing an agent can work on: either no
392
+ // checkboxes at all, or only checkboxes with no text after them.
338
393
  const tracksFilename = path.basename(tracksFile);
339
394
  state = 'blocked';
340
- instruction = `The ${tracksFilename} file exists but contains no tasks.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
395
+ instruction = `The ${tracksFilename} file exists but contains no tasks to work on.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
341
396
  }
342
397
  else if (tracksFile && remaining === 0 && total > 0) {
343
398
  state = 'all_done';
@@ -352,58 +407,6 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
352
407
  state = 'ready';
353
408
  instruction = schemaInstruction?.trim() ?? 'Read context files, work through pending tasks, mark complete as you go.\nPause if you hit blockers or need clarification.';
354
409
  }
355
- let missingComprehension;
356
- let comprehension;
357
- let applyReadyEmitted = false;
358
- await trackArtifactContentChanges({ changeDir, changeName, contextFiles });
359
- await maybeEmitProposalReady({
360
- changeDir,
361
- changeName,
362
- schema: context.schemaName,
363
- missingArtifacts,
364
- artifactCount: schema.artifacts.length,
365
- contextFiles,
366
- });
367
- if (state === 'ready') {
368
- const specPaths = contextFiles.specs ?? [];
369
- const tasksPath = tracksFile && tracksFileExists ? path.join(changeDir, tracksFile) : null;
370
- const planPath = resolvePlanPath(contextFiles, changeDir);
371
- const pendingTaskCount = tasks.filter((task) => !task.done).length;
372
- const artifactPresence = buildArtifactPresence(contextFiles, pendingTaskCount);
373
- const gate = checkComprehensionGate(changeDir, specPaths, options.projectConfig ?? readProjectConfig(projectRoot), { tasksPath, planPath, pendingTaskCount, artifactPresence });
374
- if (gate.active && !gate.passed && gate.info) {
375
- state = 'blocked';
376
- missingComprehension = true;
377
- comprehension = gate.info;
378
- instruction = `Complete the comprehension quiz in /opsx:apply before implementation (score ≥ ${gate.info.thresholdPercent}% on proposal, design, specs, plan, and tasks; plan receives the majority of questions per questionAllocation).`;
379
- }
380
- else if (gate.active && gate.info) {
381
- comprehension = gate.info;
382
- }
383
- applyReadyEmitted = await maybeEmitApplyReady({
384
- changeDir,
385
- changeName,
386
- state,
387
- contextFiles,
388
- });
389
- if (gate.active && gate.info) {
390
- await trackComprehensionGateChecked({
391
- changeDir,
392
- changeName,
393
- passed: gate.passed,
394
- gateInfo: gate.info,
395
- state: gate.passed ? 'ready' : 'blocked',
396
- contextFiles,
397
- });
398
- if (!gate.passed && gate.info.bestScorePercent !== undefined) {
399
- await trackComprehensionRetakeRequired({
400
- changeDir,
401
- changeName,
402
- gateInfo: gate.info,
403
- });
404
- }
405
- }
406
- }
407
410
  return {
408
411
  changeName,
409
412
  changeDir,
@@ -413,11 +416,11 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
413
416
  tasks,
414
417
  state,
415
418
  missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
416
- missingComprehension,
417
- comprehension,
418
419
  instruction,
419
- applyReadyEmitted,
420
+ comprehension: comprehensionStatus,
421
+ applyReadyEmitted: comprehensionStatus.passed,
420
422
  ...(references !== undefined ? { references } : {}),
423
+ ...operationInputs,
421
424
  };
422
425
  }
423
426
  export async function applyInstructionsCommand(options) {
@@ -435,119 +438,9 @@ export async function applyInstructionsCommand(options) {
435
438
  if (options.schema) {
436
439
  validateSchemaExists(options.schema, projectRoot);
437
440
  }
441
+ // One parsed config snapshot supplies schema fallback, references, context,
442
+ // and operation guidance for this command.
438
443
  const { projectConfig, references } = await loadRootConfigContext(root);
439
- if (options.recordComprehensionPass) {
440
- if (options.score === undefined) {
441
- spinner?.stop();
442
- throw new Error('--score is required with --record-comprehension-pass');
443
- }
444
- const context = loadChangeContext(projectRoot, changeName, options.schema, {
445
- changeDir: getChangeDir(planningHome, changeName),
446
- planningHome,
447
- });
448
- const changeDir = context.changeDir;
449
- const schema = resolveSchema(context.schemaName, projectRoot);
450
- const specPaths = [];
451
- const specsArtifact = schema.artifacts.find((a) => a.id === 'specs');
452
- if (specsArtifact) {
453
- specPaths.push(...resolveArtifactOutputs(changeDir, specsArtifact.generates));
454
- }
455
- const tracksFile = schema.apply?.tracks ?? null;
456
- const tasksPath = tracksFile ? path.join(changeDir, tracksFile) : null;
457
- const planPath = fs.existsSync(path.join(changeDir, 'plan.md'))
458
- ? path.join(changeDir, 'plan.md')
459
- : null;
460
- const pendingTaskCount = tasksPath && fs.existsSync(tasksPath)
461
- ? parseTasksFile(fs.readFileSync(tasksPath, 'utf-8')).filter((t) => !t.done).length
462
- : 0;
463
- const contextFilesForPresence = {};
464
- for (const artifact of schema.artifacts) {
465
- const outputs = resolveArtifactOutputs(changeDir, artifact.generates);
466
- if (outputs.length > 0) {
467
- contextFilesForPresence[artifact.id] = outputs;
468
- }
469
- }
470
- const artifactPresence = buildArtifactPresence(contextFilesForPresence, pendingTaskCount);
471
- const questionCount = resolveComprehensionQuestionCount(options.questionCount, specPaths, projectConfig, pendingTaskCount, artifactPresence);
472
- const { attempt, failureCountBefore } = await incrementComprehensionAttempt(changeDir);
473
- try {
474
- const record = recordComprehensionPass({
475
- changeDir,
476
- specPaths,
477
- tasksPath,
478
- planPath,
479
- projectConfig,
480
- scorePercent: options.score,
481
- attempt,
482
- questionCount,
483
- pendingTaskCount,
484
- artifactPresence,
485
- });
486
- spinner?.stop();
487
- const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
488
- planningHome,
489
- references,
490
- projectConfig,
491
- });
492
- await emitComprehensionAttemptAfterPass({
493
- changeDir,
494
- changeName,
495
- attempt: record.attempt,
496
- failureCountBefore,
497
- scorePercent: record.score_percent,
498
- thresholdPercent: record.threshold_percent,
499
- questionCount: record.question_count,
500
- contextFiles: instructions.contextFiles,
501
- applyReadyEmitted: instructions.applyReadyEmitted ?? false,
502
- });
503
- if (options.json) {
504
- console.log(JSON.stringify({
505
- recorded: true,
506
- comprehensionPass: record,
507
- ...instructions,
508
- root: toRootOutput(root),
509
- }, null, 2));
510
- return;
511
- }
512
- console.log(`Comprehension pass recorded (${record.score_percent}%).`);
513
- printApplyInstructionsText(instructions);
514
- return;
515
- }
516
- catch (error) {
517
- spinner?.stop();
518
- if (error instanceof ComprehensionPassError) {
519
- await incrementComprehensionFailureCount(changeDir);
520
- await trackComprehensionAttempt({
521
- changeDir,
522
- changeName,
523
- attempt,
524
- scorePercent: options.score,
525
- thresholdPercent: error.threshold,
526
- questionCount,
527
- passed: false,
528
- failureCountBefore,
529
- contextFiles: contextFilesForPresence,
530
- });
531
- if (options.json) {
532
- console.log(JSON.stringify({
533
- recorded: false,
534
- status: [
535
- {
536
- severity: 'error',
537
- code: 'comprehension_score_below_threshold',
538
- message: error.message,
539
- },
540
- ],
541
- root: toRootOutput(root),
542
- }, null, 2));
543
- process.exitCode = 1;
544
- return;
545
- }
546
- }
547
- throw error;
548
- }
549
- }
550
- // generateApplyInstructions uses loadChangeContext which auto-detects schema
551
444
  const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
552
445
  planningHome,
553
446
  references,
@@ -566,7 +459,7 @@ export async function applyInstructionsCommand(options) {
566
459
  }
567
460
  }
568
461
  export function printApplyInstructionsText(instructions) {
569
- const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, missingComprehension, comprehension, instruction, } = instructions;
462
+ const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
570
463
  console.log(`## Apply: ${changeName}`);
571
464
  console.log(`Schema: ${schemaName}`);
572
465
  console.log();
@@ -582,24 +475,6 @@ export function printApplyInstructionsText(instructions) {
582
475
  console.log('Use the openspec-continue-change skill to create these first.');
583
476
  console.log();
584
477
  }
585
- if (state === 'blocked' && missingComprehension && comprehension) {
586
- console.log('### ⚠️ Comprehension Required');
587
- console.log();
588
- console.log(`Pass the comprehension quiz (score ≥ ${comprehension.thresholdPercent}%) before implementation.`);
589
- console.log(`Questions: ${comprehension.questionCount} (${comprehension.optionsPerQuestion} options each)`);
590
- const allocationParts = Object.entries(comprehension.questionAllocation)
591
- .filter(([, count]) => (count ?? 0) > 0)
592
- .map(([category, count]) => `${category}×${count}`);
593
- if (allocationParts.length > 0) {
594
- console.log(`Allocation: ${allocationParts.join(', ')}`);
595
- }
596
- console.log(`Specs: ${comprehension.requirementCount} requirements, ${comprehension.scenarioCount} scenarios; Tasks: ${comprehension.pendingTaskCount} pending`);
597
- if (comprehension.bestScorePercent !== undefined) {
598
- console.log(`Previous score: ${comprehension.bestScorePercent}% (artifacts changed — retake required)`);
599
- }
600
- console.log('Complete the quiz via /opsx:apply.');
601
- console.log();
602
- }
603
478
  // Context files (dynamically from schema)
604
479
  const contextFileEntries = Object.entries(contextFiles);
605
480
  if (contextFileEntries.length > 0) {
@@ -634,125 +509,57 @@ export function printApplyInstructionsText(instructions) {
634
509
  // Instruction
635
510
  console.log('### Instruction');
636
511
  console.log(instruction);
512
+ console.log();
513
+ printOperationInputsText(instructions);
637
514
  }
638
- // -----------------------------------------------------------------------------
639
- // Modify Instructions Command (pre-apply artifact revision)
640
- // -----------------------------------------------------------------------------
641
- function buildModifyInstruction(sourceArtifact, downstreamArtifacts, modifyInput) {
642
- const downstreamLine = downstreamArtifacts.length > 0
643
- ? `Downstream artifacts to refresh: ${downstreamArtifacts.join(', ')}.`
644
- : 'No downstream artifacts depend on this source.';
645
- const requestLine = modifyInput
646
- ? `User modify request: ${modifyInput}`
647
- : 'Apply the user modify request from the conversation.';
648
- return [
649
- `Revise the "${sourceArtifact}" artifact for this change (pre-apply only).`,
650
- requestLine,
651
- downstreamLine,
652
- 'For each artifact in artifactsToUpdate (in order): run openspec instructions <id> --change <name> --json, read upstream dependencies, apply a surgical edit aligned with the modify request, and write to resolvedOutputPath.',
653
- 'When proposal capabilities change, add/remove/rename specs under specs/ accordingly.',
654
- 'After all updates, run openspec status --change <name> --json, then hand off to /opsx:apply.',
655
- ].join('\n');
515
+ export function generateArchiveInstructions(changeName, projectConfig) {
516
+ return {
517
+ changeName,
518
+ ...loadOperationInputs(projectConfig, 'archive'),
519
+ };
656
520
  }
657
- export async function modifyInstructionsCommand(options) {
521
+ export async function archiveInstructionsCommand(options) {
658
522
  const root = await resolveRootForCommand(options, { json: options.json });
659
523
  if (!root) {
660
524
  return;
661
525
  }
662
- const spinner = options.json ? undefined : ora('Generating modify instructions...').start();
526
+ const spinner = options.json ? undefined : ora('Loading archive inputs...').start();
663
527
  try {
664
- const planningHome = toPlanningHome(root);
665
- const projectRoot = root.path;
666
- const changeName = await validateChangeExists(options.change, projectRoot, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
667
- if (options.schema) {
668
- validateSchemaExists(options.schema, projectRoot);
669
- }
670
- const sourceArtifact = options.artifact?.trim();
671
- if (!sourceArtifact) {
672
- spinner?.stop();
673
- throw new Error('Missing required option --artifact. Specify the artifact to modify (e.g., design, proposal, tasks).');
674
- }
675
- const modifyInput = await resolveWorkflowInputAsync({
676
- workflowInput: options.workflowInput,
677
- workflowInputFile: options.workflowInputFile,
678
- });
679
- const editor = normalizeEditor(options.editor);
680
- const context = loadChangeContext(projectRoot, changeName, options.schema, {
681
- changeDir: getChangeDir(planningHome, changeName),
682
- planningHome,
683
- });
684
- const artifact = context.graph.getArtifact(sourceArtifact);
685
- if (!artifact) {
686
- spinner?.stop();
687
- const validIds = context.graph.getAllArtifacts().map((a) => a.id);
688
- throw new Error(`Artifact '${sourceArtifact}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
689
- }
690
- const applyInstructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
691
- planningHome,
692
- });
693
- if (applyInstructions.progress.complete > 0) {
694
- spinner?.stop();
695
- throw new Error(`Apply has already started (${applyInstructions.progress.complete}/${applyInstructions.progress.total} tasks complete). /opsx:modify is pre-apply only. Edit artifacts manually or start a new change.`);
696
- }
697
- if (applyInstructions.missingArtifacts && applyInstructions.missingArtifacts.length > 0) {
698
- spinner?.stop();
699
- throw new Error(`Change is not ready to modify. Missing artifacts: ${applyInstructions.missingArtifacts.join(', ')}. Use /opsx:continue or /opsx:propose to create them first.`);
700
- }
701
- const sourceOutputs = resolveArtifactOutputs(context.changeDir, artifact.generates);
702
- if (sourceOutputs.length === 0) {
703
- spinner?.stop();
704
- throw new Error(`Artifact '${sourceArtifact}' does not exist yet. Use /opsx:continue to create it first.`);
705
- }
706
- const downstreamArtifacts = context.graph.getTransitiveDependents(sourceArtifact);
707
- const artifactsToUpdate = [sourceArtifact, ...downstreamArtifacts];
708
- const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
709
- await trackArtifactModifyRequested({
710
- changeDir: context.changeDir,
711
- changeName,
712
- schema: context.schemaName,
713
- sourceArtifactId: sourceArtifact,
714
- downstreamArtifactIds: downstreamArtifacts,
715
- artifactsToUpdate,
716
- modifyInput,
717
- editor,
718
- });
719
- const payload = {
720
- changeName,
721
- schemaName: context.schemaName,
722
- sourceArtifact,
723
- ...(modifyInput ? { modifyInput } : {}),
724
- downstreamArtifacts,
725
- artifactsToUpdate,
726
- changeRoot: context.changeDir,
727
- artifactPaths: status.artifactPaths,
728
- phase: 'pre_apply',
729
- instruction: buildModifyInstruction(sourceArtifact, downstreamArtifacts, modifyInput),
730
- };
528
+ const changeName = await validateChangeExists(options.change, root.path, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
529
+ const projectConfig = readProjectConfig(root.path);
530
+ const instructions = generateArchiveInstructions(changeName, projectConfig);
731
531
  spinner?.stop();
732
532
  if (options.json) {
733
- console.log(JSON.stringify({ ...payload, root: toRootOutput(root) }, null, 2));
533
+ console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
734
534
  return;
735
535
  }
736
- printModifyInstructionsText(payload);
536
+ printArchiveInstructionsText(instructions);
737
537
  }
738
538
  catch (error) {
739
539
  spinner?.stop();
740
540
  throw error;
741
541
  }
742
542
  }
743
- export function printModifyInstructionsText(instructions) {
744
- console.log(`## Modify: ${instructions.changeName}`);
745
- console.log(`Schema: ${instructions.schemaName}`);
746
- console.log(`Phase: ${instructions.phase}`);
543
+ export function printArchiveInstructionsText(instructions) {
544
+ console.log(`## Archive Inputs: ${instructions.changeName}`);
747
545
  console.log();
748
- console.log(`Source artifact: ${instructions.sourceArtifact}`);
749
- if (instructions.modifyInput) {
750
- console.log(`Modify request: ${instructions.modifyInput}`);
546
+ printOperationInputsText(instructions);
547
+ }
548
+ function printOperationInputsText(inputs) {
549
+ if (inputs.context) {
550
+ console.log('### Project Context (required instruction input)');
551
+ console.log(inputs.context);
552
+ console.log();
553
+ }
554
+ if (inputs.operationGuidance && inputs.operationGuidance.length > 0) {
555
+ console.log('### Operation Guidance (advisory)');
556
+ for (const guidance of inputs.operationGuidance) {
557
+ console.log(`- ${guidance}`);
558
+ }
559
+ console.log();
560
+ }
561
+ if (!inputs.context && !inputs.operationGuidance) {
562
+ console.log('No project context or operation guidance configured.');
751
563
  }
752
- console.log(`Downstream: ${instructions.downstreamArtifacts.length > 0 ? instructions.downstreamArtifacts.join(', ') : '(none)'}`);
753
- console.log(`Artifacts to update: ${instructions.artifactsToUpdate.join(', ')}`);
754
- console.log();
755
- console.log('### Instruction');
756
- console.log(instructions.instruction);
757
564
  }
758
565
  //# sourceMappingURL=instructions.js.map