oh-my-codex 0.16.3 → 0.16.4
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.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/README.md +3 -3
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -0
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/cli/__tests__/cleanup.test.js +27 -0
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -5
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +101 -6
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +131 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +47 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +2 -2
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +272 -26
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +85 -3
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +1 -1
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +108 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +69 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +54 -8
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +8 -4
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +9 -0
- package/dist/cli/codex-feature-probe.d.ts.map +1 -0
- package/dist/cli/codex-feature-probe.js +28 -0
- package/dist/cli/codex-feature-probe.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +152 -17
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +9 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +135 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-parity.js +8 -8
- package/dist/cli/mcp-parity.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +3 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +88 -0
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +21 -0
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +4 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +7 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +5 -3
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +114 -44
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +64 -5
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts +2 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +12 -3
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
- package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
- package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
- package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
- package/dist/config/__tests__/codex-hooks.test.js +7 -0
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +70 -9
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +116 -11
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
- package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
- package/dist/config/codex-feature-flags.d.ts +21 -0
- package/dist/config/codex-feature-flags.d.ts.map +1 -0
- package/dist/config/codex-feature-flags.js +56 -0
- package/dist/config/codex-feature-flags.js.map +1 -0
- package/dist/config/codex-hooks.d.ts +2 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +25 -3
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +11 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +221 -123
- package/dist/config/generator.js.map +1 -1
- package/dist/config/omx-first-party-mcp.d.ts +3 -1
- package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
- package/dist/config/omx-first-party-mcp.js +2 -2
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +92 -2
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +125 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
- package/dist/hooks/agents-overlay.js +2 -2
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +7 -5
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +164 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +4 -5
- package/dist/hud/state.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +61 -0
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +166 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +23 -2
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +22 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js +57 -26
- package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +5 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
- package/dist/planning/__tests__/artifacts.test.js +533 -4
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/__tests__/context-pack-status.test.js +524 -0
- package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
- package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
- package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
- package/dist/planning/__tests__/markdown-structure.test.js +459 -0
- package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +523 -1
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -1
- package/dist/planning/artifacts.d.ts +1 -1
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +227 -28
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/planning/context-pack-status.d.ts +25 -0
- package/dist/planning/context-pack-status.d.ts.map +1 -1
- package/dist/planning/context-pack-status.js +272 -31
- package/dist/planning/context-pack-status.js.map +1 -1
- package/dist/planning/markdown-structure.d.ts +20 -0
- package/dist/planning/markdown-structure.d.ts.map +1 -0
- package/dist/planning/markdown-structure.js +137 -0
- package/dist/planning/markdown-structure.js.map +1 -0
- package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
- package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
- package/dist/ralph/__tests__/completion-audit.test.js +121 -0
- package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
- package/dist/ralph/completion-audit.d.ts +8 -0
- package/dist/ralph/completion-audit.d.ts.map +1 -0
- package/dist/ralph/completion-audit.js +99 -0
- package/dist/ralph/completion-audit.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +220 -13
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +133 -54
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +4 -2
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-dispatcher.js +30 -1
- package/dist/scripts/notify-dispatcher.js.map +1 -1
- package/dist/scripts/notify-hook.js +3 -1
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +102 -27
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +9 -3
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +7 -0
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +25 -8
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +1 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +22 -15
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.js +3 -3
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js +39 -0
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +5 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +497 -2
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +1 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +8 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/approved-execution.d.ts.map +1 -1
- package/dist/team/approved-execution.js +3 -0
- package/dist/team/approved-execution.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +43 -0
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +4 -0
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +2 -6
- package/dist/team/state.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +124 -1
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +44 -2
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +197 -13
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/wiki/lifecycle.js +1 -1
- package/dist/wiki/lifecycle.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/.mcp.json +5 -5
- package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
- package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +21 -24
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
- package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
- package/skills/analyze/SKILL.md +0 -2
- package/skills/ask-claude/SKILL.md +5 -3
- package/skills/ask-gemini/SKILL.md +5 -3
- package/skills/autopilot/SKILL.md +2 -2
- package/skills/code-review/SKILL.md +1 -3
- package/skills/deep-interview/SKILL.md +5 -7
- package/skills/doctor/SKILL.md +2 -2
- package/skills/ecomode/SKILL.md +105 -1
- package/skills/frontend-ui-ux/SKILL.md +4 -26
- package/skills/git-master/SKILL.md +2 -4
- package/skills/omx-setup/SKILL.md +3 -3
- package/skills/pipeline/SKILL.md +3 -3
- package/skills/plan/SKILL.md +3 -6
- package/skills/ralph/SKILL.md +9 -10
- package/skills/swarm/SKILL.md +5 -3
- package/skills/tdd/SKILL.md +95 -1
- package/skills/ultragoal/SKILL.md +36 -3
- package/skills/ultraqa/SKILL.md +21 -24
- package/skills/ultrawork/SKILL.md +8 -8
- package/skills/web-clone/SKILL.md +348 -1
- package/skills/wiki/SKILL.md +13 -13
- package/src/scripts/__tests__/codex-native-hook.test.ts +231 -13
- package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
- package/src/scripts/codex-native-hook.ts +160 -43
- package/src/scripts/codex-native-pre-post.ts +4 -1
- package/src/scripts/notify-dispatcher.ts +40 -1
- package/src/scripts/notify-hook.ts +3 -1
|
@@ -68,6 +68,25 @@ async function writeContextPack(slug, prdPath, testSpecPath, roles) {
|
|
|
68
68
|
})),
|
|
69
69
|
}, null, 2));
|
|
70
70
|
}
|
|
71
|
+
async function writeContextPackWithEntries(slug, prdPath, testSpecPath, entries) {
|
|
72
|
+
const packPath = join(tempDir, canonicalContextPackRelativePath(slug));
|
|
73
|
+
const prdActual = await readFile(prdPath, 'utf-8');
|
|
74
|
+
const testSpecActual = await readFile(testSpecPath, 'utf-8');
|
|
75
|
+
await writeFile(packPath, JSON.stringify({
|
|
76
|
+
slug,
|
|
77
|
+
basis: {
|
|
78
|
+
prd: {
|
|
79
|
+
path: relativeToRepo(prdPath),
|
|
80
|
+
sha1: computeGitBlobSha1(prdActual),
|
|
81
|
+
},
|
|
82
|
+
testSpecs: [{
|
|
83
|
+
path: relativeToRepo(testSpecPath),
|
|
84
|
+
sha1: computeGitBlobSha1(testSpecActual),
|
|
85
|
+
}],
|
|
86
|
+
},
|
|
87
|
+
entries,
|
|
88
|
+
}, null, 2));
|
|
89
|
+
}
|
|
71
90
|
describe('context pack handoff status', () => {
|
|
72
91
|
beforeEach(async () => { await setup(); });
|
|
73
92
|
afterEach(async () => { await cleanup(); });
|
|
@@ -126,6 +145,8 @@ describe('context pack handoff status', () => {
|
|
|
126
145
|
assert.equal(status.contextPackStatus, 'plan-only');
|
|
127
146
|
assert.equal(status.baselineState, 'present');
|
|
128
147
|
assert.equal(status.outcomeState, 'absent');
|
|
148
|
+
assert.equal(status.declarationState, 'unknown');
|
|
149
|
+
assert.equal(status.contextPackRoleRefs, null);
|
|
129
150
|
assert.equal(status.roleCoverage, 'unknown');
|
|
130
151
|
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
131
152
|
assert.deepEqual(status.contextPackIssues, []);
|
|
@@ -143,11 +164,313 @@ describe('context pack handoff status', () => {
|
|
|
143
164
|
assert.deepEqual(status.contextPack, { path: packPath });
|
|
144
165
|
assert.equal(status.contextPackStatus, 'ready');
|
|
145
166
|
assert.equal(status.packState, 'valid');
|
|
167
|
+
assert.equal(status.declarationState, 'matching');
|
|
146
168
|
assert.equal(status.roleCoverage, 'covered');
|
|
147
169
|
assert.equal(status.basisState, 'fresh');
|
|
170
|
+
assert.deepEqual(status.contextPackRoleRefs, {
|
|
171
|
+
scope: ['src/scope-0.ts'],
|
|
172
|
+
build: ['src/build-1.ts'],
|
|
173
|
+
verify: ['src/verify-2.ts'],
|
|
174
|
+
});
|
|
148
175
|
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
149
176
|
assert.deepEqual(status.contextPackIssues, []);
|
|
150
177
|
});
|
|
178
|
+
it('keeps the handoff state machine invariant under private metadata variations and counterfactuals', async () => {
|
|
179
|
+
const cases = [
|
|
180
|
+
{
|
|
181
|
+
slug: 'ready-valid-private-metadata',
|
|
182
|
+
entries: [
|
|
183
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
184
|
+
{
|
|
185
|
+
path: 'src/build.ts',
|
|
186
|
+
roles: ['build'],
|
|
187
|
+
label: 'Runtime Contract',
|
|
188
|
+
tags: ['runtime', 'build'],
|
|
189
|
+
selector: { type: 'heading', value: '## Runtime Contract', maxWords: 120 },
|
|
190
|
+
relationPath: [
|
|
191
|
+
{ tag: 'plan', target: 'ready-valid-private-metadata' },
|
|
192
|
+
{ tag: 'implements', target: 'src/build.ts#runtime-contract' },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
path: 'src/verify.ts',
|
|
197
|
+
roles: ['verify'],
|
|
198
|
+
selector: { type: 'lines', start: 2, end: 4 },
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
expected: {
|
|
202
|
+
status: 'ready',
|
|
203
|
+
packState: 'valid',
|
|
204
|
+
declarationState: 'matching',
|
|
205
|
+
basisState: 'fresh',
|
|
206
|
+
roleCoverage: 'covered',
|
|
207
|
+
roleRefs: {
|
|
208
|
+
scope: ['src/scope.ts'],
|
|
209
|
+
build: ['src/build.ts'],
|
|
210
|
+
verify: ['src/verify.ts'],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
slug: 'ready-invalid-private-metadata',
|
|
216
|
+
entries: [
|
|
217
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
218
|
+
{
|
|
219
|
+
path: 'src/build.ts',
|
|
220
|
+
roles: ['build'],
|
|
221
|
+
selector: { type: 'heading', value: 'Runtime Contract', maxWords: 20 },
|
|
222
|
+
},
|
|
223
|
+
{ path: 'src/verify.ts', roles: ['verify'] },
|
|
224
|
+
],
|
|
225
|
+
expected: {
|
|
226
|
+
status: 'ready',
|
|
227
|
+
packState: 'valid',
|
|
228
|
+
declarationState: 'matching',
|
|
229
|
+
basisState: 'fresh',
|
|
230
|
+
roleCoverage: 'covered',
|
|
231
|
+
roleRefs: {
|
|
232
|
+
scope: ['src/scope.ts'],
|
|
233
|
+
build: ['src/build.ts'],
|
|
234
|
+
verify: ['src/verify.ts'],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
slug: 'incomplete-invalid-private-metadata',
|
|
240
|
+
entries: [
|
|
241
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
242
|
+
{
|
|
243
|
+
path: 'src/build.ts',
|
|
244
|
+
roles: ['build'],
|
|
245
|
+
relationPath: [
|
|
246
|
+
{ tag: 'plan', target: 'alpha' },
|
|
247
|
+
{ tag: 'evidence', target: 'beta' },
|
|
248
|
+
{ tag: 'implements', target: 'gamma' },
|
|
249
|
+
{ tag: 'dependency', target: 'delta' },
|
|
250
|
+
{ tag: 'bounds', target: 'epsilon' },
|
|
251
|
+
{ tag: 'verifies', target: 'zeta' },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
expected: {
|
|
256
|
+
status: 'incomplete',
|
|
257
|
+
packState: 'valid',
|
|
258
|
+
declarationState: 'matching',
|
|
259
|
+
basisState: 'fresh',
|
|
260
|
+
roleCoverage: 'missing-required-roles',
|
|
261
|
+
roleRefs: null,
|
|
262
|
+
missingRoles: ['verify'],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
slug: 'invalid-stale-private-metadata',
|
|
267
|
+
entries: [
|
|
268
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
269
|
+
{
|
|
270
|
+
path: 'src/build.ts',
|
|
271
|
+
roles: ['build'],
|
|
272
|
+
tags: ['runtime', '', 'verify'],
|
|
273
|
+
},
|
|
274
|
+
{ path: 'src/verify.ts', roles: ['verify'] },
|
|
275
|
+
],
|
|
276
|
+
mutate: async ({ testSpecPath }) => {
|
|
277
|
+
await writeFile(testSpecPath, '# Drifted Test Spec\n');
|
|
278
|
+
},
|
|
279
|
+
expected: {
|
|
280
|
+
status: 'invalid',
|
|
281
|
+
packState: 'valid',
|
|
282
|
+
declarationState: 'matching',
|
|
283
|
+
basisState: 'stale',
|
|
284
|
+
roleCoverage: 'covered',
|
|
285
|
+
roleRefs: null,
|
|
286
|
+
issue: 'basis test-spec hash',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
for (const testCase of cases) {
|
|
291
|
+
const fixture = await writeApprovedPlan(testCase.slug, [
|
|
292
|
+
'# PRD',
|
|
293
|
+
'',
|
|
294
|
+
buildContextPackOutcome(canonicalContextPackRelativePath(testCase.slug)),
|
|
295
|
+
'',
|
|
296
|
+
`Launch via omx ralph ${JSON.stringify(`Execute ${testCase.slug} plan`)}`,
|
|
297
|
+
]);
|
|
298
|
+
await writeContextPackWithEntries(testCase.slug, fixture.prdPath, fixture.testSpecPath, testCase.entries);
|
|
299
|
+
if (testCase.mutate) {
|
|
300
|
+
await testCase.mutate(fixture);
|
|
301
|
+
}
|
|
302
|
+
const status = readContextPackHandoffStatus(tempDir, fixture.prdPath);
|
|
303
|
+
assert.equal(status.contextPackStatus, testCase.expected.status, `status mismatch for ${testCase.slug}`);
|
|
304
|
+
assert.equal(status.packState, testCase.expected.packState, `packState mismatch for ${testCase.slug}`);
|
|
305
|
+
assert.equal(status.declarationState, testCase.expected.declarationState, `declarationState mismatch for ${testCase.slug}`);
|
|
306
|
+
assert.equal(status.basisState, testCase.expected.basisState, `basisState mismatch for ${testCase.slug}`);
|
|
307
|
+
assert.equal(status.roleCoverage, testCase.expected.roleCoverage, `roleCoverage mismatch for ${testCase.slug}`);
|
|
308
|
+
assert.deepEqual(status.contextPackRoleRefs, testCase.expected.roleRefs, `role refs mismatch for ${testCase.slug}`);
|
|
309
|
+
assert.deepEqual(status.missingRequiredContextPackRoles, testCase.expected.missingRoles ?? [], `missing roles mismatch for ${testCase.slug}`);
|
|
310
|
+
if (testCase.expected.issue) {
|
|
311
|
+
assert.ok(status.contextPackIssues.some((issue) => issue.includes(testCase.expected.issue ?? '')), `expected issue containing ${testCase.expected.issue} for ${testCase.slug}`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
assert.deepEqual(status.contextPackIssues, [], `issues should stay empty for ${testCase.slug}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
it('keeps the public handoff state machine invariant across exhaustive private metadata modes', async () => {
|
|
319
|
+
const metadataVariants = [
|
|
320
|
+
{
|
|
321
|
+
name: 'none',
|
|
322
|
+
buildMetadata: () => ({}),
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'valid-heading',
|
|
326
|
+
buildMetadata: (slug) => ({
|
|
327
|
+
label: 'Runtime Contract',
|
|
328
|
+
tags: ['runtime', 'build'],
|
|
329
|
+
selector: { type: 'heading', value: ' ## Runtime Contract ', maxWords: 120 },
|
|
330
|
+
relationPath: [
|
|
331
|
+
{ tag: 'Plan', target: ` ${slug} ` },
|
|
332
|
+
{ tag: 'Implements', target: ' src/build.ts#runtime-contract ' },
|
|
333
|
+
],
|
|
334
|
+
}),
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'valid-lines',
|
|
338
|
+
buildMetadata: (slug) => ({
|
|
339
|
+
label: 'Verify Slice',
|
|
340
|
+
selector: { type: 'lines', start: 2, end: 5 },
|
|
341
|
+
relationPath: [
|
|
342
|
+
{ tag: 'Plan', target: ` ${slug} ` },
|
|
343
|
+
{ tag: 'Verifies', target: ' src/build.ts:2-5 ' },
|
|
344
|
+
],
|
|
345
|
+
}),
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'invalid-label',
|
|
349
|
+
buildMetadata: () => ({ label: '---' }),
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'invalid-tags',
|
|
353
|
+
buildMetadata: () => ({ tags: ['runtime', '', 'verify'] }),
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'invalid-selector-extra-key',
|
|
357
|
+
buildMetadata: () => ({
|
|
358
|
+
selector: { type: 'heading', value: 'Runtime Contract', extra: true },
|
|
359
|
+
}),
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'invalid-relation-too-many-steps',
|
|
363
|
+
buildMetadata: () => ({
|
|
364
|
+
relationPath: [
|
|
365
|
+
{ tag: 'plan', target: 'alpha' },
|
|
366
|
+
{ tag: 'evidence', target: 'beta' },
|
|
367
|
+
{ tag: 'dependency', target: 'gamma' },
|
|
368
|
+
{ tag: 'links-to', target: 'delta' },
|
|
369
|
+
{ tag: 'bounds', target: 'epsilon' },
|
|
370
|
+
{ tag: 'verifies', target: 'zeta' },
|
|
371
|
+
],
|
|
372
|
+
}),
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'invalid-relation-step-extra-key',
|
|
376
|
+
buildMetadata: () => ({
|
|
377
|
+
relationPath: [{ tag: 'plan', target: 'alpha', extra: true }],
|
|
378
|
+
}),
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'invalid-entry-extra-key',
|
|
382
|
+
buildMetadata: () => ({ unexpected: true }),
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
const scenarios = [
|
|
386
|
+
{
|
|
387
|
+
name: 'ready',
|
|
388
|
+
buildEntries: (metadata) => [
|
|
389
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
390
|
+
{ path: 'src/build.ts', roles: ['build'], ...metadata },
|
|
391
|
+
{ path: 'src/verify.ts', roles: ['verify'] },
|
|
392
|
+
],
|
|
393
|
+
expected: {
|
|
394
|
+
status: 'ready',
|
|
395
|
+
packState: 'valid',
|
|
396
|
+
declarationState: 'matching',
|
|
397
|
+
basisState: 'fresh',
|
|
398
|
+
roleCoverage: 'covered',
|
|
399
|
+
roleRefs: {
|
|
400
|
+
scope: ['src/scope.ts'],
|
|
401
|
+
build: ['src/build.ts'],
|
|
402
|
+
verify: ['src/verify.ts'],
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: 'incomplete',
|
|
408
|
+
buildEntries: (metadata) => [
|
|
409
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
410
|
+
{ path: 'src/build.ts', roles: ['build'], ...metadata },
|
|
411
|
+
],
|
|
412
|
+
expected: {
|
|
413
|
+
status: 'incomplete',
|
|
414
|
+
packState: 'valid',
|
|
415
|
+
declarationState: 'matching',
|
|
416
|
+
basisState: 'fresh',
|
|
417
|
+
roleCoverage: 'missing-required-roles',
|
|
418
|
+
roleRefs: null,
|
|
419
|
+
missingRoles: ['verify'],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: 'stale',
|
|
424
|
+
buildEntries: (metadata) => [
|
|
425
|
+
{ path: 'src/scope.ts', roles: ['scope'] },
|
|
426
|
+
{ path: 'src/build.ts', roles: ['build'], ...metadata },
|
|
427
|
+
{ path: 'src/verify.ts', roles: ['verify'] },
|
|
428
|
+
],
|
|
429
|
+
mutate: async ({ testSpecPath }) => {
|
|
430
|
+
await writeFile(testSpecPath, '# Drifted Test Spec\n');
|
|
431
|
+
},
|
|
432
|
+
expected: {
|
|
433
|
+
status: 'invalid',
|
|
434
|
+
packState: 'valid',
|
|
435
|
+
declarationState: 'matching',
|
|
436
|
+
basisState: 'stale',
|
|
437
|
+
roleCoverage: 'covered',
|
|
438
|
+
roleRefs: null,
|
|
439
|
+
issue: 'basis test-spec hash',
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
];
|
|
443
|
+
for (const scenario of scenarios) {
|
|
444
|
+
for (const metadataVariant of metadataVariants) {
|
|
445
|
+
const slug = `${scenario.name}-${metadataVariant.name}`;
|
|
446
|
+
const fixture = await writeApprovedPlan(slug, [
|
|
447
|
+
'# PRD',
|
|
448
|
+
'',
|
|
449
|
+
buildContextPackOutcome(canonicalContextPackRelativePath(slug)),
|
|
450
|
+
'',
|
|
451
|
+
`Launch via omx ralph ${JSON.stringify(`Execute ${slug} plan`)}`,
|
|
452
|
+
]);
|
|
453
|
+
await writeContextPackWithEntries(slug, fixture.prdPath, fixture.testSpecPath, scenario.buildEntries(metadataVariant.buildMetadata(slug)));
|
|
454
|
+
if (scenario.mutate) {
|
|
455
|
+
await scenario.mutate(fixture);
|
|
456
|
+
}
|
|
457
|
+
const status = readContextPackHandoffStatus(tempDir, fixture.prdPath);
|
|
458
|
+
assert.equal(status.contextPackStatus, scenario.expected.status, `status mismatch for ${slug}`);
|
|
459
|
+
assert.equal(status.packState, scenario.expected.packState, `packState mismatch for ${slug}`);
|
|
460
|
+
assert.equal(status.declarationState, scenario.expected.declarationState, `declarationState mismatch for ${slug}`);
|
|
461
|
+
assert.equal(status.basisState, scenario.expected.basisState, `basisState mismatch for ${slug}`);
|
|
462
|
+
assert.equal(status.roleCoverage, scenario.expected.roleCoverage, `roleCoverage mismatch for ${slug}`);
|
|
463
|
+
assert.deepEqual(status.contextPackRoleRefs, scenario.expected.roleRefs, `role refs mismatch for ${slug}`);
|
|
464
|
+
assert.deepEqual(status.missingRequiredContextPackRoles, scenario.expected.missingRoles ?? [], `missing roles mismatch for ${slug}`);
|
|
465
|
+
if (scenario.expected.issue) {
|
|
466
|
+
assert.ok(status.contextPackIssues.some((issue) => issue.includes(scenario.expected.issue ?? '')), `expected issue containing ${scenario.expected.issue} for ${slug}`);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
assert.deepEqual(status.contextPackIssues, [], `issues should stay empty for ${slug}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
});
|
|
151
474
|
it('reports incomplete when the declared pack omits required execution roles', async () => {
|
|
152
475
|
const { prdPath, testSpecPath } = await writeApprovedPlan('gamma', [
|
|
153
476
|
'# PRD',
|
|
@@ -159,6 +482,9 @@ describe('context pack handoff status', () => {
|
|
|
159
482
|
await writeContextPack('gamma', prdPath, testSpecPath, ['scope']);
|
|
160
483
|
const status = readContextPackHandoffStatus(tempDir);
|
|
161
484
|
assert.equal(status.contextPackStatus, 'incomplete');
|
|
485
|
+
assert.equal(status.roleCoverage, 'missing-required-roles');
|
|
486
|
+
assert.equal(status.contextPackRoleRefs, null);
|
|
487
|
+
assert.equal(status.roleCoverage, 'missing-required-roles');
|
|
162
488
|
assert.deepEqual(status.missingRequiredContextPackRoles, ['build', 'verify']);
|
|
163
489
|
});
|
|
164
490
|
it('reports invalid when the declared pack basis drifts from the approved test spec', async () => {
|
|
@@ -174,6 +500,9 @@ describe('context pack handoff status', () => {
|
|
|
174
500
|
const status = readContextPackHandoffStatus(tempDir);
|
|
175
501
|
assert.equal(status.contextPackStatus, 'invalid');
|
|
176
502
|
assert.equal(status.roleCoverage, 'covered');
|
|
503
|
+
assert.equal(status.basisState, 'stale');
|
|
504
|
+
assert.equal(status.contextPackRoleRefs, null);
|
|
505
|
+
assert.equal(status.basisState, 'stale');
|
|
177
506
|
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
178
507
|
assert.ok(status.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
179
508
|
});
|
|
@@ -190,6 +519,7 @@ describe('context pack handoff status', () => {
|
|
|
190
519
|
const status = readContextPackHandoffStatus(tempDir);
|
|
191
520
|
assert.equal(status.contextPackStatus, 'invalid');
|
|
192
521
|
assert.equal(status.roleCoverage, 'missing-required-roles');
|
|
522
|
+
assert.equal(status.contextPackRoleRefs, null);
|
|
193
523
|
assert.deepEqual(status.missingRequiredContextPackRoles, ['build', 'verify']);
|
|
194
524
|
assert.ok(status.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
195
525
|
});
|
|
@@ -205,6 +535,7 @@ describe('context pack handoff status', () => {
|
|
|
205
535
|
const status = readContextPackHandoffStatus(tempDir);
|
|
206
536
|
assert.equal(status.contextPackStatus, 'invalid');
|
|
207
537
|
assert.equal(status.packState, 'invalid');
|
|
538
|
+
assert.equal(status.contextPackRoleRefs, null);
|
|
208
539
|
assert.equal(status.roleCoverage, 'unknown');
|
|
209
540
|
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
210
541
|
assert.ok(status.contextPackIssues.some((issue) => issue.includes('invalid JSON')));
|
|
@@ -225,6 +556,97 @@ describe('context pack handoff status', () => {
|
|
|
225
556
|
assert.equal(status.contextPackStatus, 'plan-only');
|
|
226
557
|
assert.equal(status.outcomeState, 'absent');
|
|
227
558
|
});
|
|
559
|
+
it('ignores indented outcome declarations and keeps the plan in plan-only status', async () => {
|
|
560
|
+
await writeApprovedPlan('epsilon-indented', [
|
|
561
|
+
'# PRD',
|
|
562
|
+
'',
|
|
563
|
+
' ## Context Pack Outcome',
|
|
564
|
+
'',
|
|
565
|
+
` - pack: created \`${canonicalContextPackRelativePath('epsilon-indented')}\``,
|
|
566
|
+
'',
|
|
567
|
+
'Launch via omx ralph "Execute epsilon indented plan"',
|
|
568
|
+
]);
|
|
569
|
+
const status = readContextPackHandoffStatus(tempDir);
|
|
570
|
+
assert.equal(status.contextPackStatus, 'plan-only');
|
|
571
|
+
assert.equal(status.outcomeState, 'absent');
|
|
572
|
+
});
|
|
573
|
+
it('ignores commented outcome declarations and keeps the plan in plan-only status', async () => {
|
|
574
|
+
await writeApprovedPlan('epsilon-commented', [
|
|
575
|
+
'# PRD',
|
|
576
|
+
'',
|
|
577
|
+
'<!--',
|
|
578
|
+
'## Context Pack Outcome',
|
|
579
|
+
'',
|
|
580
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-commented-hidden')}\``,
|
|
581
|
+
'<!--',
|
|
582
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-commented-hidden-deeper')}\``,
|
|
583
|
+
'-->',
|
|
584
|
+
'-->',
|
|
585
|
+
'',
|
|
586
|
+
'Launch via omx ralph "Execute epsilon commented plan"',
|
|
587
|
+
]);
|
|
588
|
+
const status = readContextPackHandoffStatus(tempDir);
|
|
589
|
+
assert.equal(status.contextPackStatus, 'plan-only');
|
|
590
|
+
assert.equal(status.outcomeState, 'absent');
|
|
591
|
+
});
|
|
592
|
+
it('ignores adversarial hidden outcome declarations and still reads the visible declaration', async () => {
|
|
593
|
+
const { prdPath, testSpecPath } = await writeApprovedPlan('epsilon-adversarial', [
|
|
594
|
+
'# PRD',
|
|
595
|
+
'',
|
|
596
|
+
'```md',
|
|
597
|
+
'## Context Pack Outcome',
|
|
598
|
+
'',
|
|
599
|
+
`- pack: created \`${canonicalContextPackRelativePath('sample-hidden')}\``,
|
|
600
|
+
' ```',
|
|
601
|
+
'```still-open',
|
|
602
|
+
'~~~',
|
|
603
|
+
'## Context Pack Outcome',
|
|
604
|
+
'',
|
|
605
|
+
`- pack: created \`${canonicalContextPackRelativePath('other-hidden')}\``,
|
|
606
|
+
'```',
|
|
607
|
+
'',
|
|
608
|
+
buildContextPackOutcome(canonicalContextPackRelativePath('epsilon-adversarial')),
|
|
609
|
+
'',
|
|
610
|
+
'Launch via omx ralph "Execute epsilon adversarial plan"',
|
|
611
|
+
]);
|
|
612
|
+
await writeContextPack('epsilon-adversarial', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
613
|
+
const status = readContextPackHandoffStatus(tempDir);
|
|
614
|
+
assert.equal(status.contextPackStatus, 'ready');
|
|
615
|
+
assert.equal(status.outcomeState, 'declared');
|
|
616
|
+
assert.deepEqual(status.contextPack, {
|
|
617
|
+
path: join(tempDir, canonicalContextPackRelativePath('epsilon-adversarial')),
|
|
618
|
+
});
|
|
619
|
+
assert.deepEqual(status.contextPackIssues, []);
|
|
620
|
+
});
|
|
621
|
+
it('keeps the visible outcome section valid when nested hidden blocks appear inside it', async () => {
|
|
622
|
+
const { prdPath, testSpecPath } = await writeApprovedPlan('epsilon-inner-hidden', [
|
|
623
|
+
'# PRD',
|
|
624
|
+
'',
|
|
625
|
+
'## Context Pack Outcome',
|
|
626
|
+
'',
|
|
627
|
+
'<!--',
|
|
628
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-inner-hidden-comment')}\``,
|
|
629
|
+
'<!--',
|
|
630
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-inner-hidden-comment-deeper')}\``,
|
|
631
|
+
'-->',
|
|
632
|
+
'-->',
|
|
633
|
+
'```md',
|
|
634
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-inner-hidden-fenced')}\``,
|
|
635
|
+
'```',
|
|
636
|
+
` - pack: created \`${canonicalContextPackRelativePath('epsilon-inner-hidden-indented')}\``,
|
|
637
|
+
`- pack: created \`${canonicalContextPackRelativePath('epsilon-inner-hidden')}\``,
|
|
638
|
+
'',
|
|
639
|
+
'Launch via omx ralph "Execute epsilon inner hidden plan"',
|
|
640
|
+
]);
|
|
641
|
+
await writeContextPack('epsilon-inner-hidden', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
642
|
+
const status = readContextPackHandoffStatus(tempDir);
|
|
643
|
+
assert.equal(status.contextPackStatus, 'ready');
|
|
644
|
+
assert.equal(status.outcomeState, 'declared');
|
|
645
|
+
assert.deepEqual(status.contextPack, {
|
|
646
|
+
path: join(tempDir, canonicalContextPackRelativePath('epsilon-inner-hidden')),
|
|
647
|
+
});
|
|
648
|
+
assert.deepEqual(status.contextPackIssues, []);
|
|
649
|
+
});
|
|
228
650
|
it('rejects nested outcome paths that are not canonical context-pack files', async () => {
|
|
229
651
|
await writeApprovedPlan('eta', [
|
|
230
652
|
'# PRD',
|
|
@@ -249,6 +671,7 @@ describe('context pack handoff status', () => {
|
|
|
249
671
|
const status = readContextPackHandoffStatus(tempDir);
|
|
250
672
|
assert.equal(status.contextPackStatus, 'invalid');
|
|
251
673
|
assert.equal(status.outcomeState, 'declared');
|
|
674
|
+
assert.equal(status.declarationState, 'mismatched');
|
|
252
675
|
assert.equal(status.packState, 'invalid');
|
|
253
676
|
assert.ok(status.contextPackIssues.some((issue) => issue.includes('does not match approved plan slug theta')));
|
|
254
677
|
});
|
|
@@ -267,5 +690,106 @@ describe('context pack handoff status', () => {
|
|
|
267
690
|
assert.equal(status.outcomeState, 'ambiguous');
|
|
268
691
|
assert.ok(status.contextPackIssues.some((issue) => issue.includes('multiple Context Pack Outcome sections')));
|
|
269
692
|
});
|
|
693
|
+
it('distinguishes missing, unreadable, invalid, stale, mismatched, and missing-role handoffs', async () => {
|
|
694
|
+
const cases = [
|
|
695
|
+
{
|
|
696
|
+
slug: 'missing-pack',
|
|
697
|
+
mutate: async () => { },
|
|
698
|
+
expected: {
|
|
699
|
+
status: 'incomplete',
|
|
700
|
+
packState: 'missing',
|
|
701
|
+
declarationState: 'matching',
|
|
702
|
+
basisState: 'stale',
|
|
703
|
+
roleCoverage: 'unknown',
|
|
704
|
+
issue: 'file is missing',
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
slug: 'unreadable-pack',
|
|
709
|
+
mutate: async ({ packPath }) => { await mkdir(packPath, { recursive: true }); },
|
|
710
|
+
expected: {
|
|
711
|
+
status: 'invalid',
|
|
712
|
+
packState: 'unreadable',
|
|
713
|
+
declarationState: 'matching',
|
|
714
|
+
basisState: 'stale',
|
|
715
|
+
roleCoverage: 'unknown',
|
|
716
|
+
issue: 'could not be read',
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
slug: 'invalid-pack',
|
|
721
|
+
mutate: async ({ packPath }) => { await writeFile(packPath, '{bad json'); },
|
|
722
|
+
expected: {
|
|
723
|
+
status: 'invalid',
|
|
724
|
+
packState: 'invalid',
|
|
725
|
+
declarationState: 'matching',
|
|
726
|
+
basisState: 'stale',
|
|
727
|
+
roleCoverage: 'unknown',
|
|
728
|
+
issue: 'invalid JSON',
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
slug: 'stale-basis',
|
|
733
|
+
mutate: async ({ prdPath, testSpecPath }) => {
|
|
734
|
+
await writeContextPack('stale-basis', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
735
|
+
await writeFile(testSpecPath, '# Drifted Test Spec\n');
|
|
736
|
+
},
|
|
737
|
+
expected: {
|
|
738
|
+
status: 'invalid',
|
|
739
|
+
packState: 'valid',
|
|
740
|
+
declarationState: 'matching',
|
|
741
|
+
basisState: 'stale',
|
|
742
|
+
roleCoverage: 'covered',
|
|
743
|
+
issue: 'basis test-spec hash',
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
slug: 'missing-role',
|
|
748
|
+
mutate: async ({ prdPath, testSpecPath }) => {
|
|
749
|
+
await writeContextPack('missing-role', prdPath, testSpecPath, ['scope']);
|
|
750
|
+
},
|
|
751
|
+
expected: {
|
|
752
|
+
status: 'incomplete',
|
|
753
|
+
packState: 'valid',
|
|
754
|
+
declarationState: 'matching',
|
|
755
|
+
basisState: 'fresh',
|
|
756
|
+
roleCoverage: 'missing-required-roles',
|
|
757
|
+
missingRoles: ['build', 'verify'],
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
];
|
|
761
|
+
for (const testCase of cases) {
|
|
762
|
+
const fixture = await writeApprovedPlan(testCase.slug, [
|
|
763
|
+
'# PRD',
|
|
764
|
+
'',
|
|
765
|
+
buildContextPackOutcome(canonicalContextPackRelativePath(testCase.slug)),
|
|
766
|
+
'',
|
|
767
|
+
`Launch via omx ralph "Execute ${testCase.slug} plan"`,
|
|
768
|
+
]);
|
|
769
|
+
await testCase.mutate(fixture);
|
|
770
|
+
const status = readContextPackHandoffStatus(tempDir, fixture.prdPath);
|
|
771
|
+
assert.equal(status.contextPackStatus, testCase.expected.status);
|
|
772
|
+
assert.equal(status.packState, testCase.expected.packState);
|
|
773
|
+
assert.equal(status.declarationState, testCase.expected.declarationState);
|
|
774
|
+
assert.equal(status.basisState, testCase.expected.basisState);
|
|
775
|
+
assert.equal(status.roleCoverage, testCase.expected.roleCoverage);
|
|
776
|
+
assert.deepEqual(status.missingRequiredContextPackRoles, testCase.expected.missingRoles ?? []);
|
|
777
|
+
if (testCase.expected.issue) {
|
|
778
|
+
assert.ok(status.contextPackIssues.some((issue) => issue.includes(testCase.expected.issue ?? '')), `expected issue containing ${testCase.expected.issue}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const mismatch = await writeApprovedPlan('declared-mismatch', [
|
|
782
|
+
'# PRD',
|
|
783
|
+
'',
|
|
784
|
+
buildContextPackOutcome(canonicalContextPackRelativePath('other')),
|
|
785
|
+
'',
|
|
786
|
+
'Launch via omx ralph "Execute mismatch plan"',
|
|
787
|
+
]);
|
|
788
|
+
const mismatchStatus = readContextPackHandoffStatus(tempDir, mismatch.prdPath);
|
|
789
|
+
assert.equal(mismatchStatus.contextPackStatus, 'invalid');
|
|
790
|
+
assert.equal(mismatchStatus.declarationState, 'mismatched');
|
|
791
|
+
assert.equal(mismatchStatus.packState, 'invalid');
|
|
792
|
+
assert.ok(mismatchStatus.contextPackIssues.some((issue) => issue.includes('does not match approved plan slug')));
|
|
793
|
+
});
|
|
270
794
|
});
|
|
271
795
|
//# sourceMappingURL=context-pack-status.test.js.map
|