oh-my-codex 0.17.0 → 0.17.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 (178) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/dist/cli/__tests__/question.test.js +2 -0
  4. package/dist/cli/__tests__/question.test.js.map +1 -1
  5. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -0
  6. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  7. package/dist/cli/__tests__/ralph.test.js +0 -124
  8. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  9. package/dist/cli/__tests__/setup-install-mode.test.js +156 -3
  10. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  11. package/dist/cli/__tests__/setup-refresh.test.js +3 -3
  12. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  13. package/dist/cli/__tests__/team.test.js +166 -42
  14. package/dist/cli/__tests__/team.test.js.map +1 -1
  15. package/dist/cli/doctor.js +9 -4
  16. package/dist/cli/doctor.js.map +1 -1
  17. package/dist/cli/plugin-marketplace.d.ts +5 -1
  18. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  19. package/dist/cli/plugin-marketplace.js +31 -2
  20. package/dist/cli/plugin-marketplace.js.map +1 -1
  21. package/dist/cli/question.d.ts +1 -1
  22. package/dist/cli/question.d.ts.map +1 -1
  23. package/dist/cli/question.js +98 -4
  24. package/dist/cli/question.js.map +1 -1
  25. package/dist/cli/ralph.d.ts.map +1 -1
  26. package/dist/cli/ralph.js +1 -49
  27. package/dist/cli/ralph.js.map +1 -1
  28. package/dist/cli/setup.d.ts +1 -0
  29. package/dist/cli/setup.d.ts.map +1 -1
  30. package/dist/cli/setup.js +75 -9
  31. package/dist/cli/setup.js.map +1 -1
  32. package/dist/cli/team.d.ts.map +1 -1
  33. package/dist/cli/team.js +21 -29
  34. package/dist/cli/team.js.map +1 -1
  35. package/dist/config/generator.d.ts +4 -0
  36. package/dist/config/generator.d.ts.map +1 -1
  37. package/dist/config/generator.js +58 -0
  38. package/dist/config/generator.js.map +1 -1
  39. package/dist/hooks/__tests__/agents-overlay.test.js +29 -0
  40. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  41. package/dist/hooks/__tests__/session.test.js +126 -1
  42. package/dist/hooks/__tests__/session.test.js.map +1 -1
  43. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  44. package/dist/hooks/agents-overlay.js +6 -3
  45. package/dist/hooks/agents-overlay.js.map +1 -1
  46. package/dist/hooks/session.d.ts +11 -3
  47. package/dist/hooks/session.d.ts.map +1 -1
  48. package/dist/hooks/session.js +68 -6
  49. package/dist/hooks/session.js.map +1 -1
  50. package/dist/hud/__tests__/reconcile.test.js +63 -0
  51. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  52. package/dist/hud/__tests__/tmux.test.d.ts +2 -0
  53. package/dist/hud/__tests__/tmux.test.d.ts.map +1 -0
  54. package/dist/hud/__tests__/tmux.test.js +92 -0
  55. package/dist/hud/__tests__/tmux.test.js.map +1 -0
  56. package/dist/hud/reconcile.d.ts +2 -0
  57. package/dist/hud/reconcile.d.ts.map +1 -1
  58. package/dist/hud/reconcile.js +14 -1
  59. package/dist/hud/reconcile.js.map +1 -1
  60. package/dist/hud/tmux.d.ts +12 -0
  61. package/dist/hud/tmux.d.ts.map +1 -1
  62. package/dist/hud/tmux.js +88 -0
  63. package/dist/hud/tmux.js.map +1 -1
  64. package/dist/mcp/__tests__/hermes-bridge.test.js +82 -1
  65. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  66. package/dist/mcp/hermes-bridge.d.ts +34 -1
  67. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  68. package/dist/mcp/hermes-bridge.js +75 -0
  69. package/dist/mcp/hermes-bridge.js.map +1 -1
  70. package/dist/mcp/hermes-server.d.ts +111 -6
  71. package/dist/mcp/hermes-server.d.ts.map +1 -1
  72. package/dist/mcp/hermes-server.js +38 -1
  73. package/dist/mcp/hermes-server.js.map +1 -1
  74. package/dist/pipeline/__tests__/stages.test.js +18 -9
  75. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  76. package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
  77. package/dist/pipeline/stages/team-exec.js +2 -7
  78. package/dist/pipeline/stages/team-exec.js.map +1 -1
  79. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +111 -269
  80. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  81. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +31 -72
  82. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -1
  83. package/dist/planning/__tests__/artifacts.test.js +27 -372
  84. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  85. package/dist/planning/artifacts.d.ts +1 -14
  86. package/dist/planning/artifacts.d.ts.map +1 -1
  87. package/dist/planning/artifacts.js +11 -31
  88. package/dist/planning/artifacts.js.map +1 -1
  89. package/dist/question/__tests__/state.test.js +349 -1
  90. package/dist/question/__tests__/state.test.js.map +1 -1
  91. package/dist/question/__tests__/ui.test.js +6 -6
  92. package/dist/question/__tests__/ui.test.js.map +1 -1
  93. package/dist/question/events.d.ts +53 -0
  94. package/dist/question/events.d.ts.map +1 -0
  95. package/dist/question/events.js +201 -0
  96. package/dist/question/events.js.map +1 -0
  97. package/dist/question/state.d.ts +30 -2
  98. package/dist/question/state.d.ts.map +1 -1
  99. package/dist/question/state.js +276 -5
  100. package/dist/question/state.js.map +1 -1
  101. package/dist/question/types.d.ts +1 -0
  102. package/dist/question/types.d.ts.map +1 -1
  103. package/dist/question/types.js.map +1 -1
  104. package/dist/question/ui.d.ts.map +1 -1
  105. package/dist/question/ui.js +3 -18
  106. package/dist/question/ui.js.map +1 -1
  107. package/dist/ralph/__tests__/completion-audit.test.js +39 -0
  108. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -1
  109. package/dist/scripts/__tests__/codex-native-hook.test.js +111 -1
  110. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  111. package/dist/scripts/__tests__/run-test-files.test.js +22 -0
  112. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  113. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  114. package/dist/scripts/codex-native-hook.js +93 -1
  115. package/dist/scripts/codex-native-hook.js.map +1 -1
  116. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  117. package/dist/scripts/codex-native-pre-post.js +12 -6
  118. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  119. package/dist/scripts/run-test-files.js +12 -1
  120. package/dist/scripts/run-test-files.js.map +1 -1
  121. package/dist/team/__tests__/approved-execution.test.js +25 -24
  122. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  123. package/dist/team/__tests__/runtime.test.js +173 -26
  124. package/dist/team/__tests__/runtime.test.js.map +1 -1
  125. package/dist/team/__tests__/scaling.test.js +66 -17
  126. package/dist/team/__tests__/scaling.test.js.map +1 -1
  127. package/dist/team/__tests__/tmux-session.test.js +42 -0
  128. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  129. package/dist/team/__tests__/worker-bootstrap.test.js +205 -0
  130. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  131. package/dist/team/approved-execution.d.ts +13 -0
  132. package/dist/team/approved-execution.d.ts.map +1 -1
  133. package/dist/team/approved-execution.js +65 -30
  134. package/dist/team/approved-execution.js.map +1 -1
  135. package/dist/team/runtime.d.ts.map +1 -1
  136. package/dist/team/runtime.js +28 -24
  137. package/dist/team/runtime.js.map +1 -1
  138. package/dist/team/scaling.d.ts.map +1 -1
  139. package/dist/team/scaling.js +7 -8
  140. package/dist/team/scaling.js.map +1 -1
  141. package/dist/team/tmux-session.d.ts.map +1 -1
  142. package/dist/team/tmux-session.js +48 -2
  143. package/dist/team/tmux-session.js.map +1 -1
  144. package/dist/team/ultragoal-context.d.ts +35 -0
  145. package/dist/team/ultragoal-context.d.ts.map +1 -0
  146. package/dist/team/ultragoal-context.js +191 -0
  147. package/dist/team/ultragoal-context.js.map +1 -0
  148. package/dist/ultragoal/__tests__/docs-contract.test.js +19 -0
  149. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  152. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -3
  153. package/plugins/oh-my-codex/skills/ralph/SKILL.md +2 -2
  154. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  155. package/plugins/oh-my-codex/skills/team/SKILL.md +6 -0
  156. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -0
  157. package/skills/plan/SKILL.md +3 -3
  158. package/skills/ralph/SKILL.md +2 -2
  159. package/skills/ralplan/SKILL.md +1 -1
  160. package/skills/team/SKILL.md +6 -0
  161. package/skills/ultragoal/SKILL.md +11 -0
  162. package/src/scripts/__tests__/codex-native-hook.test.ts +133 -1
  163. package/src/scripts/__tests__/run-test-files.test.ts +32 -0
  164. package/src/scripts/codex-native-hook.ts +121 -2
  165. package/src/scripts/codex-native-pre-post.ts +12 -6
  166. package/src/scripts/run-test-files.ts +13 -2
  167. package/dist/planning/__tests__/context-pack-status.test.d.ts +0 -2
  168. package/dist/planning/__tests__/context-pack-status.test.d.ts.map +0 -1
  169. package/dist/planning/__tests__/context-pack-status.test.js +0 -795
  170. package/dist/planning/__tests__/context-pack-status.test.js.map +0 -1
  171. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +0 -2
  172. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +0 -1
  173. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +0 -612
  174. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +0 -1
  175. package/dist/planning/context-pack-status.d.ts +0 -73
  176. package/dist/planning/context-pack-status.d.ts.map +0 -1
  177. package/dist/planning/context-pack-status.js +0 -745
  178. package/dist/planning/context-pack-status.js.map +0 -1
@@ -1,745 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { dirname, relative, resolve } from 'node:path';
4
- import { planningArtifactSlug } from './artifact-names.js';
5
- import { INITIAL_MARKDOWN_VISIBILITY_STATE, inspectMarkdownLine, } from './markdown-structure.js';
6
- const CONTEXT_PACK_OUTCOME_HEADING_PATTERN = /^#{1,6}\s+Context Pack Outcome\s*$/i;
7
- const CONTEXT_PACK_OUTCOME_DECLARATION_PATTERN = /^[*-]\s*pack\s*:/i;
8
- const CONTEXT_PACK_OUTCOME_LINE_PATTERN = /^[*-]\s*pack\s*:\s*(?:(?:created|refreshed|revalidated)\s+)?(?:`(?<quotedPath>[^`]+\.json)`|(?<barePath>\S+\.json))\s*$/i;
9
- const CONTEXT_PACK_PATH_PATTERN = /^\.omx\/context\/context-(?<timestamp>\d{8}T\d{6}Z)-(?<slug>[^/]+)\.json$/i;
10
- const SHA1_PATTERN = /^[0-9a-f]{40}$/i;
11
- const CONTEXT_PACK_COMPACT_TOKEN_PATTERN = /^[a-z][a-z0-9-]*$/;
12
- const CONTEXT_PACK_LABEL_PATTERN = /^[\p{L}\p{N}][\p{L}\p{N}\p{M}-]*$/u;
13
- const MAX_CONTEXT_PACK_LABEL_LENGTH = 80;
14
- const MAX_CONTEXT_PACK_TAG_COUNT = 8;
15
- const MIN_CONTEXT_PACK_SELECTOR_MAX_WORDS = 40;
16
- const MAX_CONTEXT_PACK_SELECTOR_MAX_WORDS = 240;
17
- const MAX_CONTEXT_PACK_RELATION_PATH_STEPS = 5;
18
- const MAX_CONTEXT_PACK_RELATION_TARGET_LENGTH = 180;
19
- const CONTEXT_PACK_PRIVATE_ENTRY_KEYS = new Set([
20
- 'path',
21
- 'roles',
22
- 'label',
23
- 'tags',
24
- 'selector',
25
- 'relationPath',
26
- ]);
27
- const CONTEXT_PACK_PRIVATE_HEADING_SELECTOR_KEYS = new Set([
28
- 'type',
29
- 'value',
30
- 'maxWords',
31
- ]);
32
- const CONTEXT_PACK_PRIVATE_LINES_SELECTOR_KEYS = new Set([
33
- 'type',
34
- 'start',
35
- 'end',
36
- ]);
37
- const CONTEXT_PACK_PRIVATE_RELATION_STEP_KEYS = new Set([
38
- 'tag',
39
- 'target',
40
- ]);
41
- export const REQUIRED_CONTEXT_PACK_ROLES = ['scope', 'build', 'verify'];
42
- export function isApprovedExecutionFollowupReadyStatus(status) {
43
- return status === 'ready' || status === 'plan-only';
44
- }
45
- export function isApprovedExecutionContextReadyStatus(status) {
46
- return status === 'ready';
47
- }
48
- function normalizeRepoRelativePath(rawPath) {
49
- const trimmed = rawPath.trim().replace(/^`|`$/g, '').replaceAll('\\', '/');
50
- if (!trimmed) {
51
- return null;
52
- }
53
- const withoutLeadingDot = trimmed.startsWith('./') ? trimmed.slice(2) : trimmed;
54
- if (!withoutLeadingDot
55
- || withoutLeadingDot.startsWith('/')
56
- || /^[A-Za-z]:/.test(withoutLeadingDot)) {
57
- return null;
58
- }
59
- const segments = withoutLeadingDot
60
- .split('/')
61
- .filter((segment) => segment.length > 0);
62
- if (segments.length === 0 || segments.includes('..')) {
63
- return null;
64
- }
65
- return segments.join('/');
66
- }
67
- function hasOnlyAllowedKeys(record, allowedKeys) {
68
- return Object.keys(record).every((key) => allowedKeys.has(key));
69
- }
70
- function normalizeContextPackCompactToken(raw) {
71
- const normalized = raw
72
- .trim()
73
- .toLowerCase()
74
- .replace(/[^a-z0-9]+/g, '-')
75
- .replace(/^-+|-+$/g, '')
76
- .slice(0, MAX_CONTEXT_PACK_LABEL_LENGTH)
77
- .replace(/-+$/g, '');
78
- return CONTEXT_PACK_COMPACT_TOKEN_PATTERN.test(normalized) ? normalized : null;
79
- }
80
- function normalizeContextPackLabel(raw) {
81
- if (typeof raw !== 'string') {
82
- return null;
83
- }
84
- const normalized = Array.from(raw
85
- .normalize('NFKC')
86
- .trim()
87
- .toLowerCase()
88
- .replace(/[^\p{L}\p{N}\p{M}]+/gu, '-')
89
- .replace(/^-+|-+$/g, ''))
90
- .slice(0, MAX_CONTEXT_PACK_LABEL_LENGTH)
91
- .join('')
92
- .replace(/-+$/g, '');
93
- return CONTEXT_PACK_LABEL_PATTERN.test(normalized) ? normalized : null;
94
- }
95
- function normalizeContextPackPrivateTags(raw) {
96
- if (!Array.isArray(raw) || raw.length > MAX_CONTEXT_PACK_TAG_COUNT) {
97
- return null;
98
- }
99
- const normalized = new Set();
100
- for (const tag of raw) {
101
- if (typeof tag !== 'string') {
102
- return null;
103
- }
104
- const normalizedTag = normalizeContextPackCompactToken(tag);
105
- if (!normalizedTag) {
106
- return null;
107
- }
108
- normalized.add(normalizedTag);
109
- }
110
- return [...normalized].sort((left, right) => left.localeCompare(right));
111
- }
112
- function normalizeContextPackPrivateSelector(raw) {
113
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
114
- return null;
115
- }
116
- const record = raw;
117
- if (record.type === 'heading') {
118
- if (!hasOnlyAllowedKeys(record, CONTEXT_PACK_PRIVATE_HEADING_SELECTOR_KEYS)) {
119
- return null;
120
- }
121
- const value = typeof record.value === 'string' ? record.value.trim() : '';
122
- const rawMaxWords = record.maxWords;
123
- if (!value) {
124
- return null;
125
- }
126
- if (rawMaxWords != null) {
127
- if (typeof rawMaxWords !== 'number' || !Number.isInteger(rawMaxWords)) {
128
- return null;
129
- }
130
- if (rawMaxWords < MIN_CONTEXT_PACK_SELECTOR_MAX_WORDS
131
- || rawMaxWords > MAX_CONTEXT_PACK_SELECTOR_MAX_WORDS) {
132
- return null;
133
- }
134
- }
135
- return {
136
- type: 'heading',
137
- value,
138
- maxWords: typeof rawMaxWords === 'number' ? rawMaxWords : undefined,
139
- };
140
- }
141
- if (record.type === 'lines') {
142
- if (!hasOnlyAllowedKeys(record, CONTEXT_PACK_PRIVATE_LINES_SELECTOR_KEYS)) {
143
- return null;
144
- }
145
- const start = record.start;
146
- const end = record.end;
147
- if (typeof start !== 'number' || typeof end !== 'number') {
148
- return null;
149
- }
150
- if (!Number.isInteger(start) || !Number.isInteger(end)) {
151
- return null;
152
- }
153
- if (start < 1 || end < start) {
154
- return null;
155
- }
156
- return {
157
- type: 'lines',
158
- start,
159
- end,
160
- };
161
- }
162
- return null;
163
- }
164
- function normalizeContextPackPrivateRelationPath(raw) {
165
- if (!Array.isArray(raw)
166
- || raw.length === 0
167
- || raw.length > MAX_CONTEXT_PACK_RELATION_PATH_STEPS) {
168
- return null;
169
- }
170
- const steps = [];
171
- for (const step of raw) {
172
- if (!step || typeof step !== 'object' || Array.isArray(step)) {
173
- return null;
174
- }
175
- const record = step;
176
- if (!hasOnlyAllowedKeys(record, CONTEXT_PACK_PRIVATE_RELATION_STEP_KEYS)) {
177
- return null;
178
- }
179
- const tag = typeof record.tag === 'string'
180
- ? normalizeContextPackCompactToken(record.tag)
181
- : null;
182
- const target = typeof record.target === 'string' ? record.target.trim() : '';
183
- if (!tag
184
- || !target
185
- || target.length > MAX_CONTEXT_PACK_RELATION_TARGET_LENGTH) {
186
- return null;
187
- }
188
- steps.push({ tag, target });
189
- }
190
- return steps;
191
- }
192
- function computeGitBlobSha1(filePath) {
193
- const buffer = readFileSync(filePath);
194
- const header = Buffer.from(`blob ${buffer.length}\0`, 'utf-8');
195
- return createHash('sha1').update(header).update(buffer).digest('hex');
196
- }
197
- function extractContextPackOutcomeSections(content) {
198
- const lines = content.replace(/\r\n/g, '\n').split('\n');
199
- const sections = [];
200
- let state = INITIAL_MARKDOWN_VISIBILITY_STATE;
201
- for (let index = 0; index < lines.length; index += 1) {
202
- const line = lines[index];
203
- const inspection = inspectMarkdownLine(state, line);
204
- state = inspection.nextState;
205
- if (inspection.scanState !== 'normal') {
206
- continue;
207
- }
208
- if (!CONTEXT_PACK_OUTCOME_HEADING_PATTERN.test(inspection.visibleText.trim())) {
209
- continue;
210
- }
211
- const section = [];
212
- let sectionState = INITIAL_MARKDOWN_VISIBILITY_STATE;
213
- for (let sectionIndex = index + 1; sectionIndex < lines.length; sectionIndex += 1) {
214
- const sectionLine = lines[sectionIndex];
215
- const sectionInspection = inspectMarkdownLine(sectionState, sectionLine);
216
- sectionState = sectionInspection.nextState;
217
- if (sectionInspection.scanState !== 'normal') {
218
- continue;
219
- }
220
- const trimmed = sectionInspection.visibleText.trim();
221
- if (/^#{1,6}\s+\S/.test(trimmed)
222
- || (trimmed !== '' && !/^[*-]\s+/.test(trimmed))) {
223
- break;
224
- }
225
- section.push(sectionInspection.visibleText);
226
- }
227
- sections.push(section);
228
- }
229
- return sections;
230
- }
231
- function resolveDeclaredContextPackPath(repoRoot, rawPath) {
232
- const normalizedPath = normalizeRepoRelativePath(rawPath);
233
- if (!normalizedPath) {
234
- return null;
235
- }
236
- const pathMatch = normalizedPath.match(CONTEXT_PACK_PATH_PATTERN);
237
- if (!pathMatch?.groups?.slug) {
238
- return null;
239
- }
240
- const resolvedPath = resolve(repoRoot, normalizedPath);
241
- const roundTripPath = normalizeRepoRelativePath(relative(repoRoot, resolvedPath));
242
- if (!roundTripPath || roundTripPath !== normalizedPath) {
243
- return null;
244
- }
245
- return {
246
- normalizedPath,
247
- resolvedPath,
248
- slug: pathMatch.groups.slug,
249
- };
250
- }
251
- function inspectContextPackOutcome(repoRoot, content) {
252
- const outcomeSections = extractContextPackOutcomeSections(content);
253
- if (outcomeSections.length === 0) {
254
- return {
255
- outcomeState: 'absent',
256
- contextPack: null,
257
- declaredPackPath: null,
258
- declaredSlug: null,
259
- issues: [],
260
- };
261
- }
262
- if (outcomeSections.length > 1) {
263
- return {
264
- outcomeState: 'ambiguous',
265
- contextPack: null,
266
- declaredPackPath: null,
267
- declaredSlug: null,
268
- issues: ['Approved plan contains multiple Context Pack Outcome sections.'],
269
- };
270
- }
271
- let declarationCount = 0;
272
- let resolvedPackPath = null;
273
- let resolvedSlug = null;
274
- let resolvedPack = null;
275
- const issues = [];
276
- for (const line of outcomeSections[0]) {
277
- const trimmed = line.trim();
278
- if (!trimmed || !CONTEXT_PACK_OUTCOME_DECLARATION_PATTERN.test(trimmed)) {
279
- continue;
280
- }
281
- declarationCount += 1;
282
- const lineMatch = trimmed.match(CONTEXT_PACK_OUTCOME_LINE_PATTERN);
283
- if (!lineMatch?.groups) {
284
- issues.push(`Invalid Context Pack Outcome line: ${trimmed}`);
285
- continue;
286
- }
287
- const resolvedPath = resolveDeclaredContextPackPath(repoRoot, lineMatch.groups.quotedPath ?? lineMatch.groups.barePath);
288
- if (!resolvedPath) {
289
- issues.push('Context Pack Outcome must point to .omx/context/context-<timestamp>-<slug>.json.');
290
- continue;
291
- }
292
- if (declarationCount > 1) {
293
- issues.push('Context Pack Outcome may declare only one pack.');
294
- continue;
295
- }
296
- resolvedPackPath = resolvedPath.normalizedPath;
297
- resolvedSlug = resolvedPath.slug;
298
- resolvedPack = { path: resolvedPath.resolvedPath };
299
- }
300
- if (declarationCount === 0) {
301
- return {
302
- outcomeState: 'malformed',
303
- contextPack: null,
304
- declaredPackPath: null,
305
- declaredSlug: null,
306
- issues: ['Context Pack Outcome must declare exactly one pack.'],
307
- };
308
- }
309
- if (declarationCount > 1) {
310
- return {
311
- outcomeState: 'ambiguous',
312
- contextPack: resolvedPack,
313
- declaredPackPath: resolvedPackPath,
314
- declaredSlug: resolvedSlug,
315
- issues: issues.length > 0
316
- ? issues
317
- : ['Context Pack Outcome may declare only one pack.'],
318
- };
319
- }
320
- if (issues.length > 0 || !resolvedPack || !resolvedPackPath || !resolvedSlug) {
321
- return {
322
- outcomeState: 'malformed',
323
- contextPack: resolvedPack,
324
- declaredPackPath: resolvedPackPath,
325
- declaredSlug: resolvedSlug,
326
- issues,
327
- };
328
- }
329
- return {
330
- outcomeState: 'declared',
331
- contextPack: resolvedPack,
332
- declaredPackPath: resolvedPackPath,
333
- declaredSlug: resolvedSlug,
334
- issues: [],
335
- };
336
- }
337
- function readRawContextPackRecord(packPath) {
338
- try {
339
- const rawContent = readFileSync(packPath, 'utf-8');
340
- const rawDocument = JSON.parse(rawContent);
341
- if (!rawDocument || typeof rawDocument !== 'object' || Array.isArray(rawDocument)) {
342
- return null;
343
- }
344
- return rawDocument;
345
- }
346
- catch {
347
- return null;
348
- }
349
- }
350
- function readContextPackDocument(packPath) {
351
- let rawContent = '';
352
- try {
353
- rawContent = readFileSync(packPath, 'utf-8');
354
- }
355
- catch {
356
- return {
357
- packState: 'unreadable',
358
- document: null,
359
- issues: ['Declared context pack could not be read.'],
360
- };
361
- }
362
- let rawDocument;
363
- try {
364
- rawDocument = JSON.parse(rawContent);
365
- }
366
- catch {
367
- return {
368
- packState: 'invalid',
369
- document: null,
370
- issues: ['Declared context pack contains invalid JSON.'],
371
- };
372
- }
373
- if (!rawDocument || typeof rawDocument !== 'object' || Array.isArray(rawDocument)) {
374
- return {
375
- packState: 'invalid',
376
- document: null,
377
- issues: ['Declared context pack must be a JSON object.'],
378
- };
379
- }
380
- const documentRecord = rawDocument;
381
- const issues = [];
382
- const slug = typeof documentRecord.slug === 'string' ? documentRecord.slug.trim() : '';
383
- if (!slug) {
384
- issues.push('Declared context pack must declare a non-empty slug.');
385
- }
386
- const basisRecord = documentRecord.basis;
387
- if (!basisRecord || typeof basisRecord !== 'object' || Array.isArray(basisRecord)) {
388
- issues.push('Declared context pack must declare basis PRD and test-spec hashes.');
389
- }
390
- const prdBasisRecord = !basisRecord || typeof basisRecord !== 'object' || Array.isArray(basisRecord)
391
- ? null
392
- : basisRecord.prd;
393
- const testSpecsBasisRecord = !basisRecord || typeof basisRecord !== 'object' || Array.isArray(basisRecord)
394
- ? null
395
- : basisRecord.testSpecs;
396
- const normalizeBasisObject = (value, label) => {
397
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
398
- issues.push(`Declared context pack ${label} basis must be an object.`);
399
- return null;
400
- }
401
- const record = value;
402
- const path = typeof record.path === 'string' ? normalizeRepoRelativePath(record.path) : null;
403
- if (!path) {
404
- issues.push(`Declared context pack ${label} basis path must be repo-relative.`);
405
- }
406
- const sha1 = typeof record.sha1 === 'string' && SHA1_PATTERN.test(record.sha1.trim())
407
- ? record.sha1.trim().toLowerCase()
408
- : null;
409
- if (!sha1) {
410
- issues.push(`Declared context pack ${label} basis sha1 must be a 40-character hex string.`);
411
- }
412
- return path && sha1 ? { path, sha1 } : null;
413
- };
414
- const prdBasis = normalizeBasisObject(prdBasisRecord, 'prd');
415
- const testSpecBasis = Array.isArray(testSpecsBasisRecord)
416
- ? testSpecsBasisRecord
417
- .map((value, index) => normalizeBasisObject(value, `test-spec[${index}]`))
418
- .filter((value) => value !== null)
419
- : [];
420
- if (!Array.isArray(testSpecsBasisRecord) || testSpecBasis.length === 0) {
421
- issues.push('Declared context pack must declare at least one test-spec basis entry.');
422
- }
423
- const rawEntries = documentRecord.entries;
424
- if (!Array.isArray(rawEntries) || rawEntries.length === 0) {
425
- issues.push('Declared context pack must declare at least one entry.');
426
- }
427
- const entries = Array.isArray(rawEntries)
428
- ? rawEntries.flatMap((rawEntry) => {
429
- if (!rawEntry || typeof rawEntry !== 'object' || Array.isArray(rawEntry)) {
430
- issues.push('Declared context pack entries must be objects.');
431
- return [];
432
- }
433
- const record = rawEntry;
434
- const path = typeof record.path === 'string' ? normalizeRepoRelativePath(record.path) : null;
435
- if (!path) {
436
- issues.push('Declared context pack entries must provide a repo-relative path.');
437
- }
438
- if (!Array.isArray(record.roles) || record.roles.length === 0) {
439
- issues.push('Declared context pack entries must declare at least one role.');
440
- return [];
441
- }
442
- const roles = [...new Set(record.roles.flatMap((role) => {
443
- if (typeof role !== 'string') {
444
- issues.push('Declared context pack entry roles must be strings.');
445
- return [];
446
- }
447
- const normalizedRole = role.trim();
448
- if (!REQUIRED_CONTEXT_PACK_ROLES.includes(normalizedRole)) {
449
- issues.push(`Declared context pack entry role "${normalizedRole}" is not supported.`);
450
- return [];
451
- }
452
- return [normalizedRole];
453
- }))];
454
- if (!path || roles.length === 0) {
455
- return [];
456
- }
457
- return [{ path, roles }];
458
- })
459
- : [];
460
- if (issues.length > 0
461
- || !prdBasis
462
- || testSpecBasis.length === 0
463
- || entries.length === 0
464
- || !slug) {
465
- return {
466
- packState: 'invalid',
467
- document: null,
468
- issues,
469
- };
470
- }
471
- return {
472
- packState: 'valid',
473
- document: {
474
- slug,
475
- basis: {
476
- prd: prdBasis,
477
- testSpecs: testSpecBasis,
478
- },
479
- entries,
480
- },
481
- issues: [],
482
- };
483
- }
484
- function findMissingRequiredContextPackRoles(document) {
485
- const presentRoles = new Set(document.entries.flatMap((entry) => entry.roles));
486
- return REQUIRED_CONTEXT_PACK_ROLES.filter((role) => !presentRoles.has(role));
487
- }
488
- function emptyContextPackRoleRefs() {
489
- return { scope: [], build: [], verify: [] };
490
- }
491
- function groupContextPackRoleRefs(document) {
492
- const grouped = emptyContextPackRoleRefs();
493
- const seen = {
494
- scope: new Set(),
495
- build: new Set(),
496
- verify: new Set(),
497
- };
498
- for (const entry of document.entries) {
499
- for (const role of entry.roles) {
500
- if (seen[role].has(entry.path)) {
501
- continue;
502
- }
503
- seen[role].add(entry.path);
504
- grouped[role].push(entry.path);
505
- }
506
- }
507
- return grouped;
508
- }
509
- export function readReadyContextPackRoleRefs(packPath) {
510
- const packDocument = readContextPackDocument(packPath);
511
- if (!packDocument.document) {
512
- return null;
513
- }
514
- return groupContextPackRoleRefs(packDocument.document);
515
- }
516
- export function readReadyContextPackPrivateEntryReadModel(packPath) {
517
- const packDocument = readContextPackDocument(packPath);
518
- const rawDocument = readRawContextPackRecord(packPath);
519
- if (!packDocument.document || !rawDocument) {
520
- return null;
521
- }
522
- const rawEntries = rawDocument.entries;
523
- if (!Array.isArray(rawEntries)
524
- || rawEntries.length !== packDocument.document.entries.length) {
525
- return null;
526
- }
527
- const entries = [];
528
- for (const [index, rawEntry] of rawEntries.entries()) {
529
- if (!rawEntry || typeof rawEntry !== 'object' || Array.isArray(rawEntry)) {
530
- return null;
531
- }
532
- const baseEntry = packDocument.document.entries[index];
533
- if (!baseEntry) {
534
- return null;
535
- }
536
- const record = rawEntry;
537
- if (!hasOnlyAllowedKeys(record, CONTEXT_PACK_PRIVATE_ENTRY_KEYS)) {
538
- return null;
539
- }
540
- const label = record.label == null
541
- ? null
542
- : normalizeContextPackLabel(record.label);
543
- if (record.label != null && !label) {
544
- return null;
545
- }
546
- let tags = [];
547
- if (record.tags != null) {
548
- const normalizedTags = normalizeContextPackPrivateTags(record.tags);
549
- if (!normalizedTags) {
550
- return null;
551
- }
552
- tags = normalizedTags;
553
- }
554
- const selector = record.selector == null
555
- ? null
556
- : normalizeContextPackPrivateSelector(record.selector);
557
- if (record.selector != null && !selector) {
558
- return null;
559
- }
560
- const relationPath = record.relationPath == null
561
- ? null
562
- : normalizeContextPackPrivateRelationPath(record.relationPath);
563
- if (record.relationPath != null && !relationPath) {
564
- return null;
565
- }
566
- entries.push({
567
- path: baseEntry.path,
568
- roles: [...baseEntry.roles],
569
- label,
570
- tags,
571
- selector,
572
- relationPath,
573
- });
574
- }
575
- return entries;
576
- }
577
- function validateContextPackBasis(repoRoot, prdPath, testSpecPaths, document) {
578
- const issues = [];
579
- const expectedSlug = planningArtifactSlug(prdPath, 'prd');
580
- if (!expectedSlug) {
581
- issues.push('Approved plan slug could not be resolved for context pack validation.');
582
- }
583
- else if (document.slug !== expectedSlug) {
584
- issues.push(`Declared context pack slug ${document.slug} does not match approved plan slug ${expectedSlug}.`);
585
- }
586
- const expectedPrdRelativePath = normalizeRepoRelativePath(relative(repoRoot, prdPath));
587
- if (!expectedPrdRelativePath) {
588
- issues.push('Approved plan path could not be normalized for context pack validation.');
589
- }
590
- else if (document.basis.prd.path !== expectedPrdRelativePath) {
591
- issues.push(`Declared context pack basis prd path ${document.basis.prd.path} does not match ${expectedPrdRelativePath}.`);
592
- }
593
- else if (document.basis.prd.sha1 !== computeGitBlobSha1(prdPath)) {
594
- issues.push(`Declared context pack basis prd hash for ${document.basis.prd.path} does not match the current approved PRD.`);
595
- }
596
- const expectedTestSpecMap = new Map(testSpecPaths.flatMap((testSpecPath) => {
597
- const normalizedPath = normalizeRepoRelativePath(relative(repoRoot, testSpecPath));
598
- return normalizedPath
599
- ? [[normalizedPath, computeGitBlobSha1(testSpecPath)]]
600
- : [];
601
- }));
602
- const storedTestSpecMap = new Map(document.basis.testSpecs.map((testSpec) => [testSpec.path, testSpec.sha1]));
603
- for (const [expectedPath, expectedSha1] of expectedTestSpecMap.entries()) {
604
- const storedSha1 = storedTestSpecMap.get(expectedPath);
605
- if (!storedSha1) {
606
- issues.push(`Declared context pack basis is missing test-spec ${expectedPath}.`);
607
- continue;
608
- }
609
- if (storedSha1 !== expectedSha1) {
610
- issues.push(`Declared context pack basis test-spec hash for ${expectedPath} does not match the current approved test spec.`);
611
- }
612
- }
613
- for (const storedPath of storedTestSpecMap.keys()) {
614
- if (!expectedTestSpecMap.has(storedPath)) {
615
- issues.push(`Declared context pack basis includes unexpected test-spec ${storedPath}.`);
616
- }
617
- }
618
- return issues;
619
- }
620
- export function resolveContextPackHandoffState(input) {
621
- if (input.baselineState !== 'present') {
622
- return 'missing-baseline';
623
- }
624
- if (input.outcomeState === 'absent') {
625
- return 'plan-only';
626
- }
627
- if (input.outcomeState === 'malformed' || input.outcomeState === 'ambiguous') {
628
- return 'invalid';
629
- }
630
- if (input.packState === 'missing') {
631
- return 'incomplete';
632
- }
633
- if (input.packState === 'unreadable' || input.packState === 'invalid') {
634
- return 'invalid';
635
- }
636
- if (input.basisState !== 'fresh') {
637
- return 'invalid';
638
- }
639
- if (input.roleCoverage === 'missing-required-roles') {
640
- return 'incomplete';
641
- }
642
- return 'ready';
643
- }
644
- export function resolveContextPackHandoffStatus(artifacts, selection) {
645
- const prdPath = selection.prdPath;
646
- const repoRoot = dirname(dirname(artifacts.plansDir));
647
- const baselineState = !prdPath || !existsSync(prdPath)
648
- ? 'missing-prd'
649
- : selection.testSpecPaths.length === 0
650
- ? 'missing-test-spec'
651
- : 'present';
652
- const contextPackIssues = selection.testSpecPaths.length === 0 && prdPath
653
- ? ['Approved plan is missing a matching test spec.']
654
- : [];
655
- let contextPack = null;
656
- let outcomeState = 'absent';
657
- let packState = 'missing';
658
- let roleCoverage = 'unknown';
659
- let basisState = 'stale';
660
- let declarationState = 'unknown';
661
- let contextPackRoleRefs = null;
662
- let missingRequiredContextPackRoles = [];
663
- let declarationMismatch = false;
664
- if (prdPath && existsSync(prdPath)) {
665
- try {
666
- const outcome = inspectContextPackOutcome(repoRoot, readFileSync(prdPath, 'utf-8'));
667
- outcomeState = outcome.outcomeState;
668
- contextPack = outcome.contextPack;
669
- contextPackIssues.push(...outcome.issues);
670
- const expectedSlug = planningArtifactSlug(prdPath, 'prd');
671
- if (contextPack
672
- && outcome.declaredSlug
673
- && expectedSlug
674
- && outcome.declaredSlug !== expectedSlug) {
675
- declarationMismatch = true;
676
- declarationState = 'mismatched';
677
- contextPackIssues.push(`Declared context pack slug ${outcome.declaredSlug} does not match approved plan slug ${expectedSlug}.`);
678
- }
679
- else if (outcome.outcomeState === 'declared' && outcome.declaredSlug && expectedSlug) {
680
- declarationState = 'matching';
681
- }
682
- if (outcome.outcomeState === 'declared' && contextPack) {
683
- if (!existsSync(contextPack.path)) {
684
- packState = 'missing';
685
- contextPackIssues.push(`Declared context pack file is missing: ${outcome.declaredPackPath ?? contextPack.path}.`);
686
- }
687
- else {
688
- const packDocument = readContextPackDocument(contextPack.path);
689
- packState = packDocument.packState;
690
- contextPackIssues.push(...packDocument.issues);
691
- if (packDocument.document) {
692
- missingRequiredContextPackRoles =
693
- findMissingRequiredContextPackRoles(packDocument.document);
694
- roleCoverage =
695
- missingRequiredContextPackRoles.length === 0
696
- ? 'covered'
697
- : 'missing-required-roles';
698
- if (missingRequiredContextPackRoles.length === 0) {
699
- contextPackRoleRefs = groupContextPackRoleRefs(packDocument.document);
700
- }
701
- if (baselineState === 'present') {
702
- const basisIssues = validateContextPackBasis(repoRoot, prdPath, selection.testSpecPaths, packDocument.document);
703
- if (basisIssues.length === 0) {
704
- basisState = 'fresh';
705
- }
706
- else {
707
- contextPackIssues.push(...basisIssues);
708
- }
709
- }
710
- }
711
- }
712
- }
713
- }
714
- catch {
715
- outcomeState = 'malformed';
716
- contextPackIssues.push('Approved plan could not be read while resolving context pack status.');
717
- }
718
- }
719
- if (declarationMismatch) {
720
- packState = 'invalid';
721
- }
722
- const contextPackStatus = resolveContextPackHandoffState({
723
- baselineState,
724
- outcomeState,
725
- packState,
726
- roleCoverage,
727
- basisState,
728
- });
729
- return {
730
- prdPath,
731
- testSpecPaths: selection.testSpecPaths,
732
- contextPack,
733
- contextPackStatus,
734
- baselineState,
735
- outcomeState,
736
- declarationState,
737
- packState,
738
- roleCoverage,
739
- basisState,
740
- contextPackRoleRefs: contextPackStatus === 'ready' ? contextPackRoleRefs : null,
741
- missingRequiredContextPackRoles,
742
- contextPackIssues,
743
- };
744
- }
745
- //# sourceMappingURL=context-pack-status.js.map