openplanr 3.5.0 → 3.6.0

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 (217) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/cli/commands/backlog-commands.d.ts +3 -0
  3. package/dist/cli/commands/backlog-commands.d.ts.map +1 -0
  4. package/dist/cli/commands/backlog-commands.js +60 -0
  5. package/dist/cli/commands/backlog-commands.js.map +1 -0
  6. package/dist/cli/commands/company.d.ts.map +1 -1
  7. package/dist/cli/commands/company.js +17 -0
  8. package/dist/cli/commands/company.js.map +1 -1
  9. package/dist/cli/commands/planning-artifacts.d.ts.map +1 -1
  10. package/dist/cli/commands/planning-artifacts.js +13 -5
  11. package/dist/cli/commands/planning-artifacts.js.map +1 -1
  12. package/dist/dashboard/.vite/manifest.json +1 -1
  13. package/dist/dashboard/assets/{index-B-l_941W.js → index-C_OquZVk.js} +2 -2
  14. package/dist/dashboard/assets/{index-B-l_941W.js.map → index-C_OquZVk.js.map} +1 -1
  15. package/dist/dashboard/dashboard-manifest.json +8 -8
  16. package/dist/dashboard/index.html +1 -1
  17. package/dist/services/artifact-service.d.ts.map +1 -1
  18. package/dist/services/artifact-service.js +13 -6
  19. package/dist/services/artifact-service.js.map +1 -1
  20. package/dist/services/company-export-service.d.ts +41 -0
  21. package/dist/services/company-export-service.d.ts.map +1 -0
  22. package/dist/services/company-export-service.js +429 -0
  23. package/dist/services/company-export-service.js.map +1 -0
  24. package/dist/services/company-sync-service.d.ts +24 -1
  25. package/dist/services/company-sync-service.d.ts.map +1 -1
  26. package/dist/services/company-sync-service.js +36 -19
  27. package/dist/services/company-sync-service.js.map +1 -1
  28. package/docs/CLI.md +97 -1
  29. package/lib/backlog-item.d.mts +45 -1
  30. package/lib/backlog-item.mjs +302 -34
  31. package/lib/host-packages/adapter-registry.json +4 -4
  32. package/lib/host-packages/capability-map.json +1 -1
  33. package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
  34. package/lib/host-packages/claude/openplanr/.claude-plugin/icon.png +0 -0
  35. package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +1 -1
  36. package/lib/host-packages/claude/openplanr/.openplanr-content.json +77 -77
  37. package/lib/host-packages/claude/openplanr/README.md +1 -1
  38. package/lib/host-packages/claude/openplanr/skills/ceo-review/references/note-checklist.md +131 -0
  39. package/lib/host-packages/claude/openplanr/skills/ceo-review/references/operate-advisor-contract.md +2 -7
  40. package/lib/host-packages/claude/openplanr/skills/chair-review/SKILL.md +2 -7
  41. package/lib/host-packages/claude/openplanr/skills/chair-review/references/note-checklist.md +131 -0
  42. package/lib/host-packages/claude/openplanr/skills/challenger-review/SKILL.md +3 -7
  43. package/lib/host-packages/claude/openplanr/skills/challenger-review/references/note-checklist.md +131 -0
  44. package/lib/host-packages/claude/openplanr/skills/cmo-review/references/note-checklist.md +131 -0
  45. package/lib/host-packages/claude/openplanr/skills/cmo-review/references/operate-advisor-contract.md +2 -7
  46. package/lib/host-packages/claude/openplanr/skills/coo-review/references/note-checklist.md +131 -0
  47. package/lib/host-packages/claude/openplanr/skills/coo-review/references/operate-advisor-contract.md +2 -7
  48. package/lib/host-packages/claude/openplanr/skills/cpo-review/references/note-checklist.md +131 -0
  49. package/lib/host-packages/claude/openplanr/skills/cpo-review/references/operate-advisor-contract.md +2 -7
  50. package/lib/host-packages/claude/openplanr/skills/cto-review/references/note-checklist.md +131 -0
  51. package/lib/host-packages/claude/openplanr/skills/cto-review/references/operate-advisor-contract.md +2 -7
  52. package/lib/host-packages/claude/openplanr/skills/investigate/SKILL.md +2 -3
  53. package/lib/host-packages/claude/openplanr/skills/investigate/references/backlog-filing.md +102 -0
  54. package/lib/host-packages/claude/openplanr/skills/investigate/references/findings.md +8 -16
  55. package/lib/host-packages/claude/openplanr/skills/operate/SKILL.md +73 -84
  56. package/lib/host-packages/claude/openplanr/skills/operate/references/note-checklist.md +131 -0
  57. package/lib/host-packages/claude/openplanr/skills/operate/references/return-contract.md +10 -0
  58. package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +16 -17
  59. package/lib/host-packages/claude/openplanr/skills/plan/references/planning-ids.md +53 -0
  60. package/lib/host-packages/claude/openplanr/skills/plan/references/return-contract.md +10 -0
  61. package/lib/host-packages/claude/openplanr/skills/plan-review/SKILL.md +6 -6
  62. package/lib/host-packages/claude/openplanr/skills/plan-review/references/return-contract.md +10 -0
  63. package/lib/host-packages/claude/openplanr/skills/release/SKILL.md +40 -139
  64. package/lib/host-packages/claude/openplanr/skills/release/references/cadence.md +10 -0
  65. package/lib/host-packages/claude/openplanr/skills/release/references/release-notes.md +1 -1
  66. package/lib/host-packages/claude/openplanr/skills/release/references/return-contract.md +10 -0
  67. package/lib/host-packages/claude/openplanr/skills/release/references/schemes.md +4 -2
  68. package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +37 -24
  69. package/lib/host-packages/claude/openplanr/skills/ship/references/backlog-filing.md +102 -0
  70. package/lib/host-packages/claude/openplanr/skills/ship/references/findings.md +8 -16
  71. package/lib/host-packages/claude/openplanr/skills/ship/references/return-contract.md +10 -0
  72. package/lib/host-packages/claude/openplanr/skills/spec/SKILL.md +9 -9
  73. package/lib/host-packages/claude/openplanr/skills/spec/references/return-contract.md +10 -0
  74. package/lib/host-packages/cursor/openplanr/.openplanr-content.json +74 -74
  75. package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
  76. package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/references/note-checklist.md +131 -0
  77. package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/references/operate-advisor-contract.md +2 -7
  78. package/lib/host-packages/cursor/openplanr/rules/planr-chair-review/references/note-checklist.md +131 -0
  79. package/lib/host-packages/cursor/openplanr/rules/planr-chair-review.mdc +2 -7
  80. package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review/references/note-checklist.md +131 -0
  81. package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review.mdc +3 -7
  82. package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/references/note-checklist.md +131 -0
  83. package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/references/operate-advisor-contract.md +2 -7
  84. package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/references/note-checklist.md +131 -0
  85. package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/references/operate-advisor-contract.md +2 -7
  86. package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/references/note-checklist.md +131 -0
  87. package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/references/operate-advisor-contract.md +2 -7
  88. package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/references/note-checklist.md +131 -0
  89. package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/references/operate-advisor-contract.md +2 -7
  90. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/backlog-filing.md +102 -0
  91. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/references/findings.md +8 -16
  92. package/lib/host-packages/cursor/openplanr/rules/planr-investigate.mdc +2 -3
  93. package/lib/host-packages/cursor/openplanr/rules/planr-operate/references/note-checklist.md +131 -0
  94. package/lib/host-packages/cursor/openplanr/rules/planr-operate/references/return-contract.md +10 -0
  95. package/lib/host-packages/cursor/openplanr/rules/planr-operate.mdc +73 -84
  96. package/lib/host-packages/cursor/openplanr/rules/planr-plan/references/planning-ids.md +53 -0
  97. package/lib/host-packages/cursor/openplanr/rules/planr-plan/references/return-contract.md +10 -0
  98. package/lib/host-packages/cursor/openplanr/rules/planr-plan-review/references/return-contract.md +10 -0
  99. package/lib/host-packages/cursor/openplanr/rules/planr-plan-review.mdc +6 -6
  100. package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +16 -17
  101. package/lib/host-packages/cursor/openplanr/rules/planr-release/references/cadence.md +10 -0
  102. package/lib/host-packages/cursor/openplanr/rules/planr-release/references/release-notes.md +1 -1
  103. package/lib/host-packages/cursor/openplanr/rules/planr-release/references/return-contract.md +10 -0
  104. package/lib/host-packages/cursor/openplanr/rules/planr-release/references/schemes.md +4 -2
  105. package/lib/host-packages/cursor/openplanr/rules/planr-release.mdc +40 -139
  106. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/backlog-filing.md +102 -0
  107. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/findings.md +8 -16
  108. package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/return-contract.md +10 -0
  109. package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +37 -24
  110. package/lib/host-packages/cursor/openplanr/rules/planr-spec/references/return-contract.md +10 -0
  111. package/lib/host-packages/cursor/openplanr/rules/planr-spec.mdc +9 -9
  112. package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
  113. package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
  114. package/lib/host-packages/openai/openplanr/.openplanr-content.json +89 -89
  115. package/lib/host-packages/openai/openplanr/skills/ceo-review/openplanr.skill.json +4 -4
  116. package/lib/host-packages/openai/openplanr/skills/ceo-review/references/note-checklist.md +131 -0
  117. package/lib/host-packages/openai/openplanr/skills/ceo-review/references/operate-advisor-contract.md +2 -7
  118. package/lib/host-packages/openai/openplanr/skills/chair-review/SKILL.md +2 -7
  119. package/lib/host-packages/openai/openplanr/skills/chair-review/openplanr.skill.json +4 -4
  120. package/lib/host-packages/openai/openplanr/skills/chair-review/references/note-checklist.md +131 -0
  121. package/lib/host-packages/openai/openplanr/skills/challenger-review/SKILL.md +3 -7
  122. package/lib/host-packages/openai/openplanr/skills/challenger-review/openplanr.skill.json +4 -4
  123. package/lib/host-packages/openai/openplanr/skills/challenger-review/references/note-checklist.md +131 -0
  124. package/lib/host-packages/openai/openplanr/skills/cmo-review/openplanr.skill.json +4 -4
  125. package/lib/host-packages/openai/openplanr/skills/cmo-review/references/note-checklist.md +131 -0
  126. package/lib/host-packages/openai/openplanr/skills/cmo-review/references/operate-advisor-contract.md +2 -7
  127. package/lib/host-packages/openai/openplanr/skills/coo-review/openplanr.skill.json +4 -4
  128. package/lib/host-packages/openai/openplanr/skills/coo-review/references/note-checklist.md +131 -0
  129. package/lib/host-packages/openai/openplanr/skills/coo-review/references/operate-advisor-contract.md +2 -7
  130. package/lib/host-packages/openai/openplanr/skills/cpo-review/openplanr.skill.json +4 -4
  131. package/lib/host-packages/openai/openplanr/skills/cpo-review/references/note-checklist.md +131 -0
  132. package/lib/host-packages/openai/openplanr/skills/cpo-review/references/operate-advisor-contract.md +2 -7
  133. package/lib/host-packages/openai/openplanr/skills/cto-review/openplanr.skill.json +4 -4
  134. package/lib/host-packages/openai/openplanr/skills/cto-review/references/note-checklist.md +131 -0
  135. package/lib/host-packages/openai/openplanr/skills/cto-review/references/operate-advisor-contract.md +2 -7
  136. package/lib/host-packages/openai/openplanr/skills/investigate/SKILL.md +2 -3
  137. package/lib/host-packages/openai/openplanr/skills/investigate/openplanr.skill.json +4 -4
  138. package/lib/host-packages/openai/openplanr/skills/investigate/references/backlog-filing.md +102 -0
  139. package/lib/host-packages/openai/openplanr/skills/investigate/references/findings.md +8 -16
  140. package/lib/host-packages/openai/openplanr/skills/operate/SKILL.md +73 -84
  141. package/lib/host-packages/openai/openplanr/skills/operate/openplanr.skill.json +15 -4
  142. package/lib/host-packages/openai/openplanr/skills/operate/references/note-checklist.md +131 -0
  143. package/lib/host-packages/openai/openplanr/skills/operate/references/return-contract.md +10 -0
  144. package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +16 -17
  145. package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +15 -4
  146. package/lib/host-packages/openai/openplanr/skills/plan/references/planning-ids.md +53 -0
  147. package/lib/host-packages/openai/openplanr/skills/plan/references/return-contract.md +10 -0
  148. package/lib/host-packages/openai/openplanr/skills/plan-review/SKILL.md +6 -6
  149. package/lib/host-packages/openai/openplanr/skills/plan-review/openplanr.skill.json +12 -1
  150. package/lib/host-packages/openai/openplanr/skills/plan-review/references/return-contract.md +10 -0
  151. package/lib/host-packages/openai/openplanr/skills/release/SKILL.md +40 -139
  152. package/lib/host-packages/openai/openplanr/skills/release/openplanr.skill.json +4 -4
  153. package/lib/host-packages/openai/openplanr/skills/release/references/cadence.md +10 -0
  154. package/lib/host-packages/openai/openplanr/skills/release/references/release-notes.md +1 -1
  155. package/lib/host-packages/openai/openplanr/skills/release/references/return-contract.md +10 -0
  156. package/lib/host-packages/openai/openplanr/skills/release/references/schemes.md +4 -2
  157. package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +37 -24
  158. package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +9 -9
  159. package/lib/host-packages/openai/openplanr/skills/ship/references/backlog-filing.md +102 -0
  160. package/lib/host-packages/openai/openplanr/skills/ship/references/findings.md +8 -16
  161. package/lib/host-packages/openai/openplanr/skills/ship/references/return-contract.md +10 -0
  162. package/lib/host-packages/openai/openplanr/skills/spec/SKILL.md +9 -9
  163. package/lib/host-packages/openai/openplanr/skills/spec/openplanr.skill.json +12 -1
  164. package/lib/host-packages/openai/openplanr/skills/spec/references/return-contract.md +10 -0
  165. package/package.json +1 -1
  166. package/lib/host-packages/claude/openplanr/.claude-plugin/icon.svg +0 -11
  167. package/lib/host-packages/claude/openplanr/runtime/operate/scripts/errors.mjs +0 -8
  168. package/lib/host-packages/claude/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
  169. package/lib/host-packages/claude/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
  170. package/lib/host-packages/claude/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
  171. package/lib/host-packages/claude/openplanr/skills/ceo-review/scripts/validate-note.mjs +0 -12
  172. package/lib/host-packages/claude/openplanr/skills/chair-review/scripts/validate-note.mjs +0 -12
  173. package/lib/host-packages/claude/openplanr/skills/challenger-review/scripts/validate-note.mjs +0 -12
  174. package/lib/host-packages/claude/openplanr/skills/cmo-review/scripts/validate-note.mjs +0 -12
  175. package/lib/host-packages/claude/openplanr/skills/coo-review/scripts/validate-note.mjs +0 -12
  176. package/lib/host-packages/claude/openplanr/skills/cpo-review/scripts/validate-note.mjs +0 -12
  177. package/lib/host-packages/claude/openplanr/skills/cto-review/scripts/validate-note.mjs +0 -12
  178. package/lib/host-packages/claude/openplanr/skills/investigate/scripts/backlog-item.mjs +0 -339
  179. package/lib/host-packages/claude/openplanr/skills/operate/scripts/validate-note.mjs +0 -12
  180. package/lib/host-packages/claude/openplanr/skills/plan/scripts/planning-ids.mjs +0 -166
  181. package/lib/host-packages/claude/openplanr/skills/release/scripts/collect_changes.sh +0 -85
  182. package/lib/host-packages/claude/openplanr/skills/ship/scripts/backlog-item.mjs +0 -339
  183. package/lib/host-packages/claude/openplanr/skills/ship/scripts/discover-verification.mjs +0 -108
  184. package/lib/host-packages/cursor/openplanr/rules/planr-ceo-review/scripts/validate-note.mjs +0 -12
  185. package/lib/host-packages/cursor/openplanr/rules/planr-chair-review/scripts/validate-note.mjs +0 -12
  186. package/lib/host-packages/cursor/openplanr/rules/planr-challenger-review/scripts/validate-note.mjs +0 -12
  187. package/lib/host-packages/cursor/openplanr/rules/planr-cmo-review/scripts/validate-note.mjs +0 -12
  188. package/lib/host-packages/cursor/openplanr/rules/planr-coo-review/scripts/validate-note.mjs +0 -12
  189. package/lib/host-packages/cursor/openplanr/rules/planr-cpo-review/scripts/validate-note.mjs +0 -12
  190. package/lib/host-packages/cursor/openplanr/rules/planr-cto-review/scripts/validate-note.mjs +0 -12
  191. package/lib/host-packages/cursor/openplanr/rules/planr-investigate/scripts/backlog-item.mjs +0 -339
  192. package/lib/host-packages/cursor/openplanr/rules/planr-operate/scripts/validate-note.mjs +0 -12
  193. package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/planning-ids.mjs +0 -166
  194. package/lib/host-packages/cursor/openplanr/rules/planr-release/scripts/collect_changes.sh +0 -85
  195. package/lib/host-packages/cursor/openplanr/rules/planr-ship/scripts/backlog-item.mjs +0 -339
  196. package/lib/host-packages/cursor/openplanr/rules/planr-ship/scripts/discover-verification.mjs +0 -108
  197. package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/errors.mjs +0 -8
  198. package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
  199. package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
  200. package/lib/host-packages/cursor/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
  201. package/lib/host-packages/openai/openplanr/runtime/operate/scripts/errors.mjs +0 -8
  202. package/lib/host-packages/openai/openplanr/runtime/operate/scripts/operate-review-contract.mjs +0 -428
  203. package/lib/host-packages/openai/openplanr/runtime/operate/scripts/operate-review-note.mjs +0 -686
  204. package/lib/host-packages/openai/openplanr/runtime/operate/scripts/validate-note.mjs +0 -39
  205. package/lib/host-packages/openai/openplanr/skills/ceo-review/scripts/validate-note.mjs +0 -12
  206. package/lib/host-packages/openai/openplanr/skills/chair-review/scripts/validate-note.mjs +0 -12
  207. package/lib/host-packages/openai/openplanr/skills/challenger-review/scripts/validate-note.mjs +0 -12
  208. package/lib/host-packages/openai/openplanr/skills/cmo-review/scripts/validate-note.mjs +0 -12
  209. package/lib/host-packages/openai/openplanr/skills/coo-review/scripts/validate-note.mjs +0 -12
  210. package/lib/host-packages/openai/openplanr/skills/cpo-review/scripts/validate-note.mjs +0 -12
  211. package/lib/host-packages/openai/openplanr/skills/cto-review/scripts/validate-note.mjs +0 -12
  212. package/lib/host-packages/openai/openplanr/skills/investigate/scripts/backlog-item.mjs +0 -339
  213. package/lib/host-packages/openai/openplanr/skills/operate/scripts/validate-note.mjs +0 -12
  214. package/lib/host-packages/openai/openplanr/skills/plan/scripts/planning-ids.mjs +0 -166
  215. package/lib/host-packages/openai/openplanr/skills/release/scripts/collect_changes.sh +0 -85
  216. package/lib/host-packages/openai/openplanr/skills/ship/scripts/backlog-item.mjs +0 -339
  217. package/lib/host-packages/openai/openplanr/skills/ship/scripts/discover-verification.mjs +0 -108
@@ -1,686 +0,0 @@
1
- import { Buffer } from 'node:buffer';
2
-
3
- import { SkillRuntimeError } from './errors.mjs';
4
- import {
5
- OPERATE_REVIEW_CONTRACT_VERSION,
6
- OPERATE_REVIEW_CONTRACT_VERSIONS,
7
- OPERATE_REVIEW_CONTRACTS,
8
- } from './operate-review-contract.mjs';
9
-
10
- const canonicalText = (value) => String(value).replace(/\r\n/gu, '\n');
11
- const normalizeHeading = (value) => value.trim().replace(/\s+/gu, ' ');
12
- const PLACEHOLDER = /^(?:<[^>]+>|\.{3}|tbd|todo)$/iu;
13
- const CONTRACT_DECLARATION =
14
- /^(?:>|-)\s+\*\*Contract:\*\*\s*([a-z][a-z0-9-]*)@(\d+\.\d+\.\d+)\s*$/gmu;
15
- const AUTO_CONTRACT_VERSION = 'auto';
16
-
17
- function diagnostic(code, message, details = {}) {
18
- return Object.freeze({ code, message, ...details });
19
- }
20
-
21
- function contractFor(version) {
22
- const contract = OPERATE_REVIEW_CONTRACTS[version];
23
- if (!contract) {
24
- throw new SkillRuntimeError(
25
- 'E_OPERATE_REVIEW_CONTRACT_VERSION_INVALID',
26
- `Unknown Operate review-note contract version ${String(version)}.`,
27
- { version, versions: OPERATE_REVIEW_CONTRACT_VERSIONS },
28
- );
29
- }
30
- return contract;
31
- }
32
-
33
- function declaredContract(markdown) {
34
- const declarations = [...markdown.matchAll(CONTRACT_DECLARATION)].map((match) => ({
35
- kind: match[1],
36
- version: match[2],
37
- }));
38
- if (declarations.length > 1) {
39
- throw new SkillRuntimeError(
40
- 'E_OPERATE_REVIEW_CONTRACT_DECLARATION_DUPLICATE',
41
- 'Operate review note declares more than one contract version.',
42
- { declarations },
43
- );
44
- }
45
- if (declarations.length === 0) return null;
46
- const declaration = declarations[0];
47
- const contract = OPERATE_REVIEW_CONTRACTS[declaration.version];
48
- if (!contract || contract.kind !== declaration.kind) {
49
- throw new SkillRuntimeError(
50
- 'E_OPERATE_REVIEW_CONTRACT_DECLARATION_INVALID',
51
- `Unsupported Operate review-note contract ${declaration.kind}@${declaration.version}.`,
52
- { declaration, versions: OPERATE_REVIEW_CONTRACT_VERSIONS },
53
- );
54
- }
55
- return Object.freeze(declaration);
56
- }
57
-
58
- function sectionIndex(markdown) {
59
- const headings = [...markdown.matchAll(/^##\s+(.+?)\s*$/gmu)].map((match) => ({
60
- name: normalizeHeading(match[1]),
61
- start: match.index,
62
- bodyStart: match.index + match[0].length,
63
- }));
64
- return headings.map((heading, index) =>
65
- Object.freeze({
66
- ...heading,
67
- body: markdown.slice(heading.bodyStart, headings[index + 1]?.start ?? markdown.length).trim(),
68
- }),
69
- );
70
- }
71
-
72
- function profileFor(contract, profile) {
73
- const profileContract = contract.profiles[profile];
74
- if (!profileContract) {
75
- throw new SkillRuntimeError(
76
- 'E_OPERATE_REVIEW_PROFILE_INVALID',
77
- `Unknown Operate review-note profile ${String(profile)}.`,
78
- { profile, profiles: Object.keys(contract.profiles) },
79
- );
80
- }
81
- return profileContract;
82
- }
83
-
84
- /** Identify the contract used by a current or historical Operate Markdown note. */
85
- export function detectOperateReviewNoteContract(
86
- markdown,
87
- { profile, contractVersion = AUTO_CONTRACT_VERSION } = {},
88
- ) {
89
- if (
90
- contractVersion !== AUTO_CONTRACT_VERSION &&
91
- !OPERATE_REVIEW_CONTRACT_VERSIONS.includes(contractVersion)
92
- ) {
93
- contractFor(contractVersion);
94
- }
95
- const source = canonicalText(markdown);
96
- const firstSection = source.search(/^##\s+/mu);
97
- const preamble = source.slice(0, firstSection === -1 ? source.length : firstSection);
98
- const declaration = declaredContract(preamble);
99
- if (contractVersion !== AUTO_CONTRACT_VERSION) {
100
- const contract = contractFor(contractVersion);
101
- profileFor(contract, profile);
102
- return Object.freeze({
103
- contractVersion,
104
- contractKind: contract.kind,
105
- versionSource: 'requested',
106
- declaredContractVersion: declaration?.version ?? null,
107
- });
108
- }
109
- if (declaration) {
110
- const contract = contractFor(declaration.version);
111
- profileFor(contract, profile);
112
- return Object.freeze({
113
- contractVersion: declaration.version,
114
- contractKind: contract.kind,
115
- versionSource: 'declared',
116
- declaredContractVersion: declaration.version,
117
- });
118
- }
119
-
120
- const headings = new Set(sectionIndex(source).map(({ name }) => name));
121
- const scores = OPERATE_REVIEW_CONTRACT_VERSIONS.map((version) => {
122
- const contract = contractFor(version);
123
- const profileContract = profileFor(contract, profile);
124
- return {
125
- version,
126
- score: profileContract.sections.filter((section) => headings.has(section)).length,
127
- };
128
- });
129
- const legacy = scores.find(({ version }) => version === '1.0.0');
130
- const current = scores.find(({ version }) => version === OPERATE_REVIEW_CONTRACT_VERSION);
131
- const contractVersionDetected = legacy.score > current.score ? legacy.version : current.version;
132
- const contract = contractFor(contractVersionDetected);
133
- return Object.freeze({
134
- contractVersion: contractVersionDetected,
135
- contractKind: contract.kind,
136
- versionSource: Math.max(legacy.score, current.score) > 0 ? 'structure' : 'default',
137
- declaredContractVersion: null,
138
- });
139
- }
140
-
141
- function fieldsIn(markdown) {
142
- const fields = new Map();
143
- for (const match of markdown.matchAll(/^(?:>|-)\s+\*\*([^*\n]+?):\*\*\s*(.*?)\s*$/gmu)) {
144
- const name = normalizeHeading(match[1]);
145
- if (!fields.has(name)) fields.set(name, []);
146
- fields.get(name).push(match[2]);
147
- }
148
- return fields;
149
- }
150
-
151
- function requireFields(markdown, required, diagnostics, context, allowedValues = {}) {
152
- const fields = fieldsIn(markdown);
153
- for (const field of required) {
154
- const values = fields.get(field) ?? [];
155
- if (
156
- values.length === 0 ||
157
- values.every((value) => value.length === 0 || PLACEHOLDER.test(value))
158
- ) {
159
- diagnostics.push(
160
- diagnostic(
161
- 'E_OPERATE_REVIEW_FIELD_MISSING',
162
- `${context} requires a non-empty ${field} field.`,
163
- { context, field },
164
- ),
165
- );
166
- } else if (values.length > 1) {
167
- diagnostics.push(
168
- diagnostic(
169
- 'E_OPERATE_REVIEW_FIELD_DUPLICATE',
170
- `${context} contains ${values.length} ${field} fields.`,
171
- { context, field, count: values.length },
172
- ),
173
- );
174
- } else if (allowedValues[field] && !allowedValues[field].includes(values[0])) {
175
- diagnostics.push(
176
- diagnostic(
177
- 'E_OPERATE_REVIEW_FIELD_VALUE_INVALID',
178
- `${context} has an invalid ${field} value.`,
179
- { context, field, expected: allowedValues[field], actual: values[0] },
180
- ),
181
- );
182
- }
183
- }
184
- }
185
-
186
- function validateSections(markdown, contract, diagnostics) {
187
- const headings = sectionIndex(markdown);
188
- const expected = contract.sections;
189
- const actual = headings.map(({ name }) => name);
190
- for (const section of expected) {
191
- const count = actual.filter((name) => name === section).length;
192
- if (count === 0) {
193
- diagnostics.push(
194
- diagnostic('E_OPERATE_REVIEW_SECTION_MISSING', `Required section ${section} is missing.`, {
195
- section,
196
- }),
197
- );
198
- } else if (count > 1) {
199
- diagnostics.push(
200
- diagnostic(
201
- 'E_OPERATE_REVIEW_SECTION_DUPLICATE',
202
- `Required section ${section} appears ${count} times.`,
203
- { section, count },
204
- ),
205
- );
206
- }
207
- }
208
- const unexpected = actual.filter((section) => !expected.includes(section));
209
- for (const section of unexpected) {
210
- diagnostics.push(
211
- diagnostic(
212
- 'E_OPERATE_REVIEW_SECTION_UNEXPECTED',
213
- `Unexpected top-level section ${section} is not part of this review contract.`,
214
- { section },
215
- ),
216
- );
217
- }
218
- const presentExpected = actual.filter((section) => expected.includes(section));
219
- const expectedPresentOrder = expected.filter((section) => actual.includes(section));
220
- if (JSON.stringify(presentExpected) !== JSON.stringify(expectedPresentOrder)) {
221
- diagnostics.push(
222
- diagnostic('E_OPERATE_REVIEW_SECTION_ORDER', 'Review sections are not in contract order.', {
223
- expected: expectedPresentOrder,
224
- actual: presentExpected,
225
- }),
226
- );
227
- }
228
- return new Map(headings.map(({ name, body }) => [name, body]));
229
- }
230
-
231
- function validateItems(sectionBody, itemContract, diagnostics) {
232
- if (typeof sectionBody !== 'string') return 0;
233
- const prefix = itemContract.idPrefix;
234
- const pattern = new RegExp(`^###\\s+(${prefix}[1-9][0-9]*)\\s+—\\s+(.+?)\\s*$`, 'gmu');
235
- const items = [...sectionBody.matchAll(pattern)].map((match) => ({
236
- id: match[1],
237
- headingSuffix: match[2],
238
- start: match.index,
239
- bodyStart: match.index + match[0].length,
240
- }));
241
- if (items.length === 0) {
242
- if (!sectionBody.includes(itemContract.emptyText)) {
243
- diagnostics.push(
244
- diagnostic(
245
- 'E_OPERATE_REVIEW_ITEM_OR_EMPTY_STATE_MISSING',
246
- `${itemContract.section} requires an item or the exact empty state.`,
247
- { section: itemContract.section, emptyText: itemContract.emptyText },
248
- ),
249
- );
250
- }
251
- return 0;
252
- }
253
- if (items.length > itemContract.maxItems) {
254
- diagnostics.push(
255
- diagnostic(
256
- 'E_OPERATE_REVIEW_ITEM_LIMIT',
257
- `${itemContract.section} has ${items.length} items; at most ${itemContract.maxItems} are allowed.`,
258
- { section: itemContract.section, count: items.length, maxItems: itemContract.maxItems },
259
- ),
260
- );
261
- }
262
- const expectedIds = items.map((_, index) => `${prefix}${index + 1}`);
263
- const actualIds = items.map(({ id }) => id);
264
- if (JSON.stringify(actualIds) !== JSON.stringify(expectedIds)) {
265
- diagnostics.push(
266
- diagnostic(
267
- 'E_OPERATE_REVIEW_ITEM_SEQUENCE',
268
- `${itemContract.section} item IDs must be unique and contiguous.`,
269
- { section: itemContract.section, expected: expectedIds, actual: actualIds },
270
- ),
271
- );
272
- }
273
- for (const [index, item] of items.entries()) {
274
- if (
275
- itemContract.headingSuffixPattern &&
276
- !itemContract.headingSuffixPattern.test(item.headingSuffix)
277
- ) {
278
- diagnostics.push(
279
- diagnostic(
280
- 'E_OPERATE_REVIEW_ITEM_HEADING_INVALID',
281
- `${item.id} must declare a contracted priority in its heading.`,
282
- { section: itemContract.section, itemId: item.id },
283
- ),
284
- );
285
- }
286
- const body = sectionBody
287
- .slice(item.bodyStart, items[index + 1]?.start ?? sectionBody.length)
288
- .trim();
289
- requireFields(body, itemContract.fields, diagnostics, item.id, itemContract.fieldValues);
290
- }
291
- return items.length;
292
- }
293
-
294
- function validateSectionItemLimits(sectionBodies, limits, diagnostics) {
295
- for (const [section, maxItems] of Object.entries(limits ?? {})) {
296
- const body = sectionBodies.get(section);
297
- if (typeof body !== 'string' || /^None\.\s*$/iu.test(body)) continue;
298
- const count = [...body.matchAll(/^\s*-\s+\S/gmu)].length;
299
- if (count > maxItems) {
300
- diagnostics.push(
301
- diagnostic(
302
- 'E_OPERATE_REVIEW_SECTION_ITEM_LIMIT',
303
- `${section} has ${count} items; at most ${maxItems} are allowed.`,
304
- { section, count, maxItems },
305
- ),
306
- );
307
- }
308
- }
309
- }
310
-
311
- function legacyBoardDecisions(sectionBody) {
312
- if (typeof sectionBody !== 'string') return [];
313
- const headings = [...sectionBody.matchAll(/^###\s+(D[1-9][0-9]*)\s+—\s+.+?\s*$/gmu)].map(
314
- (match) => ({
315
- id: match[1],
316
- start: match.index,
317
- bodyStart: match.index + match[0].length,
318
- }),
319
- );
320
- return headings.map((heading, index) => {
321
- const body = sectionBody
322
- .slice(heading.bodyStart, headings[index + 1]?.start ?? sectionBody.length)
323
- .trim();
324
- return Object.freeze({
325
- id: heading.id,
326
- owner: fieldsIn(body).get('Owner')?.[0] ?? null,
327
- });
328
- });
329
- }
330
-
331
- function escapedPattern(value) {
332
- return String(value).replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
333
- }
334
-
335
- function validateLegacyDecisionGate(gate, decision, diagnostics) {
336
- const statements = gate
337
- .split('\n')
338
- .map((line) => line.trim())
339
- .filter((line) => line.length > 0 && new RegExp(`\\b${decision.id}\\b`, 'u').test(line));
340
- if (statements.length !== 1) {
341
- diagnostics.push(
342
- diagnostic(
343
- 'E_OPERATE_REVIEW_HUMAN_GATE_INCOMPLETE',
344
- `Human gate must contain exactly one disposition statement for ${decision.id}.`,
345
- { section: 'Human gate', decisionId: decision.id, statementCount: statements.length },
346
- ),
347
- );
348
- return;
349
- }
350
-
351
- const statement = statements[0];
352
- const missing = [];
353
- for (const disposition of ['adopt', 'reject', 'defer']) {
354
- if (!new RegExp(`\\b${disposition}\\b`, 'iu').test(statement)) missing.push(disposition);
355
- }
356
- if (!decision.owner || !new RegExp(escapedPattern(decision.owner), 'iu').test(statement)) {
357
- missing.push('named owner');
358
- }
359
- if (!/\breason\b/iu.test(statement)) missing.push('reason');
360
- if (missing.length > 0) {
361
- diagnostics.push(
362
- diagnostic(
363
- 'E_OPERATE_REVIEW_HUMAN_GATE_INCOMPLETE',
364
- `Human gate disposition for ${decision.id} is missing: ${missing.join(', ')}.`,
365
- { section: 'Human gate', decisionId: decision.id, missing },
366
- ),
367
- );
368
- }
369
- }
370
-
371
- function absentLegacyReviewSeats(reviewTrail) {
372
- if (typeof reviewTrail !== 'string') return [];
373
- const seats = [];
374
- for (const line of reviewTrail.split('\n')) {
375
- if (!line.trim().startsWith('|')) continue;
376
- const cells = line
377
- .split('|')
378
- .slice(1, -1)
379
- .map((cell) => cell.trim());
380
- if (cells.length < 2 || !/^absent(?:\b|\s*[:—-])/iu.test(cells[1])) continue;
381
- const seat = cells[0].replace(/[`*_]/gu, '').trim();
382
- if (seat.length > 0 && !seats.includes(seat)) seats.push(seat);
383
- }
384
- return seats;
385
- }
386
-
387
- function legacyGateNamesAbsentSeat(gate, absentSeats) {
388
- return absentSeats.some((seat) =>
389
- new RegExp(
390
- `\\bre[- ]run\\s+(?:\`${escapedPattern(seat)}\`|${escapedPattern(seat)})(?=$|[^\\p{L}\\p{N}-])`,
391
- 'iu',
392
- ).test(gate),
393
- );
394
- }
395
-
396
- function validateLegacyNoDecisionGate(gate, diagnostics, absentSeats) {
397
- const contradictoryDispositions = ['adopt', 'reject', 'defer'].filter((disposition) =>
398
- new RegExp(`\\b${disposition}\\b`, 'iu').test(gate),
399
- );
400
- if (contradictoryDispositions.length > 0) {
401
- diagnostics.push(
402
- diagnostic(
403
- 'E_OPERATE_REVIEW_HUMAN_GATE_CONTRADICTORY',
404
- 'A no-decision Human gate must not offer decision dispositions.',
405
- { section: 'Human gate', dispositions: contradictoryDispositions },
406
- ),
407
- );
408
- }
409
- for (const [option, pattern] of [
410
- ['close', /\bclose\b/iu],
411
- ['supply evidence', /\bsupply\s+evidence\b/iu],
412
- ]) {
413
- if (!pattern.test(gate)) {
414
- diagnostics.push(
415
- diagnostic(
416
- 'E_OPERATE_REVIEW_HUMAN_GATE_INCOMPLETE',
417
- `A no-decision Human gate must offer ${option}.`,
418
- { section: 'Human gate', option },
419
- ),
420
- );
421
- }
422
- }
423
- if (absentSeats.length > 0 && !legacyGateNamesAbsentSeat(gate, absentSeats)) {
424
- diagnostics.push(
425
- diagnostic(
426
- 'E_OPERATE_REVIEW_HUMAN_GATE_INCOMPLETE',
427
- 'A no-decision Human gate with an absent review seat must offer re-run for a named absent seat.',
428
- { section: 'Human gate', option: 're-run <seat>', absentSeats },
429
- ),
430
- );
431
- }
432
- }
433
-
434
- function validateLegacyDecisionLedger(
435
- sectionBodies,
436
- itemCount,
437
- diagnostics,
438
- { requireExecutiveCallSection = false, requireNoDecisionOptions = false } = {},
439
- ) {
440
- const executiveCall = sectionBodies.get('Executive call') ?? '';
441
- if (requireExecutiveCallSection && executiveCall.length === 0) {
442
- diagnostics.push(
443
- diagnostic(
444
- 'E_OPERATE_REVIEW_EXECUTIVE_CALL_MISSING',
445
- 'Executive call must lead with one concise decision signal.',
446
- { section: 'Executive call' },
447
- ),
448
- );
449
- }
450
-
451
- const actionPlan = sectionBodies.get('Action plan') ?? '';
452
- if (itemCount > 0) {
453
- const header =
454
- /^\|\s*ID\s*\|\s*Priority\s*\|\s*Action\s*\|\s*Owner\s*\|\s*First step\s*\|\s*Success measure\s*\|\s*Verification\s*\|\s*Depends on\s*\|\s*$/mu;
455
- const action = /^\|\s*A[1-9][0-9]*\s*\|/mu;
456
- if (!header.test(actionPlan) || !action.test(actionPlan)) {
457
- diagnostics.push(
458
- diagnostic(
459
- 'E_OPERATE_REVIEW_ACTION_PLAN_INVALID',
460
- 'A non-empty decision queue requires the contracted action table and at least one action row.',
461
- { section: 'Action plan' },
462
- ),
463
- );
464
- } else {
465
- const actionRows = actionPlan
466
- .split('\n')
467
- .filter((line) => /^\|\s*A[1-9][0-9]*\s*\|/u.test(line))
468
- .map((line) =>
469
- line
470
- .split('|')
471
- .slice(1, -1)
472
- .map((cell) => cell.trim()),
473
- );
474
- if (actionRows.length > 7) {
475
- diagnostics.push(
476
- diagnostic(
477
- 'E_OPERATE_REVIEW_ACTION_LIMIT',
478
- `Action plan has ${actionRows.length} actions; at most 7 are allowed.`,
479
- { section: 'Action plan', count: actionRows.length, maxItems: 7 },
480
- ),
481
- );
482
- }
483
- for (const row of actionRows) {
484
- if (row.length !== 8 || row.some((cell) => cell.length === 0 || PLACEHOLDER.test(cell))) {
485
- diagnostics.push(
486
- diagnostic(
487
- 'E_OPERATE_REVIEW_ACTION_ROW_INVALID',
488
- `Action ${row[0] ?? '(unknown)'} must populate all eight contracted columns.`,
489
- { section: 'Action plan', actionId: row[0] ?? null },
490
- ),
491
- );
492
- }
493
- }
494
- }
495
- }
496
-
497
- const gate = sectionBodies.get('Human gate') ?? '';
498
- if (itemCount === 0) {
499
- if (requireNoDecisionOptions) {
500
- validateLegacyNoDecisionGate(
501
- gate,
502
- diagnostics,
503
- absentLegacyReviewSeats(sectionBodies.get('Review trail')),
504
- );
505
- }
506
- return;
507
- }
508
- const decisions = legacyBoardDecisions(sectionBodies.get('Decision queue'));
509
- for (const decision of decisions) validateLegacyDecisionGate(gate, decision, diagnostics);
510
- }
511
-
512
- function validateCurrentDecisionLedger(
513
- sectionBodies,
514
- itemCount,
515
- diagnostics,
516
- { summarySection } = {},
517
- ) {
518
- if (summarySection && (sectionBodies.get(summarySection) ?? '').trim().length === 0) {
519
- diagnostics.push(
520
- diagnostic(
521
- 'E_OPERATE_REVIEW_SUMMARY_MISSING',
522
- `${summarySection} must contain a concise decision signal.`,
523
- { section: summarySection },
524
- ),
525
- );
526
- }
527
-
528
- const actionPlan = sectionBodies.get('Action plan') ?? '';
529
- if (itemCount > 0) {
530
- const header =
531
- /^\|\s*ID\s*\|\s*Priority\s*\|\s*Action\s*\|\s*Suggested owner\s*\|\s*First step\s*\|\s*Success measure\s*\|\s*Check\s*\|\s*Depends on\s*\|\s*$/mu;
532
- const action = /^\|\s*A[1-9][0-9]*\s*\|/mu;
533
- if (!header.test(actionPlan) || !action.test(actionPlan)) {
534
- diagnostics.push(
535
- diagnostic(
536
- 'E_OPERATE_REVIEW_ACTION_PLAN_INVALID',
537
- 'A non-empty decision queue requires the contracted action table and at least one action row.',
538
- { section: 'Action plan' },
539
- ),
540
- );
541
- } else {
542
- const actionRows = actionPlan
543
- .split('\n')
544
- .filter((line) => /^\|\s*A[1-9][0-9]*\s*\|/u.test(line))
545
- .map((line) =>
546
- line
547
- .split('|')
548
- .slice(1, -1)
549
- .map((cell) => cell.trim()),
550
- );
551
- if (actionRows.length > 7) {
552
- diagnostics.push(
553
- diagnostic(
554
- 'E_OPERATE_REVIEW_ACTION_LIMIT',
555
- `Action plan has ${actionRows.length} actions; at most 7 are allowed.`,
556
- { section: 'Action plan', count: actionRows.length, maxItems: 7 },
557
- ),
558
- );
559
- }
560
- for (const row of actionRows) {
561
- if (row.length !== 8 || row.some((cell) => cell.length === 0 || PLACEHOLDER.test(cell))) {
562
- diagnostics.push(
563
- diagnostic(
564
- 'E_OPERATE_REVIEW_ACTION_ROW_INVALID',
565
- `Action ${row[0] ?? '(unknown)'} must populate all eight contracted columns.`,
566
- { section: 'Action plan', actionId: row[0] ?? null },
567
- ),
568
- );
569
- }
570
- }
571
- }
572
- }
573
- }
574
-
575
- /** Return structural quality diagnostics without changing or rejecting the note. */
576
- export function inspectOperateReviewNote(
577
- markdown,
578
- { profile, contractVersion = AUTO_CONTRACT_VERSION } = {},
579
- ) {
580
- const source = canonicalText(markdown);
581
- const detected = detectOperateReviewNoteContract(source, { profile, contractVersion });
582
- const versionedContract = contractFor(detected.contractVersion);
583
- const contract = profileFor(versionedContract, profile);
584
- const diagnostics = [];
585
- if (
586
- detected.declaredContractVersion &&
587
- detected.declaredContractVersion !== detected.contractVersion
588
- ) {
589
- diagnostics.push(
590
- diagnostic(
591
- 'E_OPERATE_REVIEW_CONTRACT_VERSION_MISMATCH',
592
- `Note declares contract ${detected.declaredContractVersion} but ${detected.contractVersion} was requested.`,
593
- {
594
- declaredContractVersion: detected.declaredContractVersion,
595
- requestedContractVersion: detected.contractVersion,
596
- },
597
- ),
598
- );
599
- }
600
- const titles = [...source.matchAll(/^#\s+\S.*$/gmu)];
601
- if (titles.length === 0) {
602
- diagnostics.push(
603
- diagnostic(
604
- 'E_OPERATE_REVIEW_TITLE_MISSING',
605
- 'Review note requires one non-empty level-one title.',
606
- ),
607
- );
608
- } else if (titles.length > 1) {
609
- diagnostics.push(
610
- diagnostic(
611
- 'E_OPERATE_REVIEW_TITLE_DUPLICATE',
612
- `Review note contains ${titles.length} level-one titles.`,
613
- { count: titles.length },
614
- ),
615
- );
616
- }
617
- const byteLength = Buffer.byteLength(source, 'utf8');
618
- if (byteLength > contract.maxBytes) {
619
- diagnostics.push(
620
- diagnostic(
621
- 'E_OPERATE_REVIEW_SIZE_LIMIT',
622
- `Review note is ${byteLength} bytes; the ${profile} limit is ${contract.maxBytes}.`,
623
- { byteLength, maxBytes: contract.maxBytes },
624
- ),
625
- );
626
- }
627
- const firstSection = source.search(/^##\s+/mu);
628
- const preamble = source.slice(0, firstSection === -1 ? source.length : firstSection);
629
- requireFields(preamble, contract.summaryFields, diagnostics, 'summary', contract.summaryValues);
630
- const sectionBodies = validateSections(source, contract, diagnostics);
631
- validateSectionItemLimits(sectionBodies, contract.sectionItemLimits, diagnostics);
632
- for (const [section, fields] of Object.entries(contract.sectionFields)) {
633
- if (!sectionBodies.has(section)) continue;
634
- const body = sectionBodies.get(section);
635
- if (body === contract.sectionEmptyStates?.[section]) continue;
636
- requireFields(body, fields, diagnostics, section);
637
- }
638
- const itemCount = validateItems(
639
- sectionBodies.get(contract.item.section),
640
- contract.item,
641
- diagnostics,
642
- );
643
- if (detected.contractVersion === '1.0.0') {
644
- if (profile === 'chair') validateLegacyDecisionLedger(sectionBodies, itemCount, diagnostics);
645
- if (profile === 'board-report') {
646
- validateLegacyDecisionLedger(sectionBodies, itemCount, diagnostics, {
647
- requireExecutiveCallSection: true,
648
- requireNoDecisionOptions: true,
649
- });
650
- }
651
- } else {
652
- if (profile === 'chair') validateCurrentDecisionLedger(sectionBodies, itemCount, diagnostics);
653
- if (profile === 'board-report') {
654
- validateCurrentDecisionLedger(sectionBodies, itemCount, diagnostics, {
655
- summarySection: 'Executive summary',
656
- });
657
- }
658
- }
659
- return Object.freeze({
660
- ok: diagnostics.length === 0,
661
- profile,
662
- contractVersion: detected.contractVersion,
663
- contractKind: detected.contractKind,
664
- versionSource: detected.versionSource,
665
- byteLength,
666
- itemCount,
667
- diagnostics: Object.freeze(diagnostics),
668
- });
669
- }
670
-
671
- /** Strict opt-in assertion for callers that explicitly require a conforming durable artifact. */
672
- export function validateOperateReviewNote(markdown, options) {
673
- const result = inspectOperateReviewNote(markdown, options);
674
- if (!result.ok) {
675
- throw new SkillRuntimeError(
676
- 'E_OPERATE_REVIEW_NOTE_INVALID',
677
- `Operate ${result.profile} note failed ${result.diagnostics.length} contract check${result.diagnostics.length === 1 ? '' : 's'}.`,
678
- {
679
- profile: result.profile,
680
- contractVersion: result.contractVersion,
681
- diagnostics: result.diagnostics,
682
- },
683
- );
684
- }
685
- return result;
686
- }