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.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/cli/__tests__/question.test.js +2 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +0 -124
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +156 -3
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +3 -3
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +166 -42
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/doctor.js +9 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +5 -1
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +31 -2
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/question.d.ts +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +98 -4
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +1 -49
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +75 -9
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +21 -29
- package/dist/cli/team.js.map +1 -1
- package/dist/config/generator.d.ts +4 -0
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +58 -0
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +29 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +126 -1
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +6 -3
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/session.d.ts +11 -3
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +68 -6
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +63 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.d.ts +2 -0
- package/dist/hud/__tests__/tmux.test.d.ts.map +1 -0
- package/dist/hud/__tests__/tmux.test.js +92 -0
- package/dist/hud/__tests__/tmux.test.js.map +1 -0
- package/dist/hud/reconcile.d.ts +2 -0
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +14 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/tmux.d.ts +12 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +88 -0
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +82 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +34 -1
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +75 -0
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/hermes-server.d.ts +111 -6
- package/dist/mcp/hermes-server.d.ts.map +1 -1
- package/dist/mcp/hermes-server.js +38 -1
- package/dist/mcp/hermes-server.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +18 -9
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +2 -7
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +111 -269
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +31 -72
- package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +27 -372
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifacts.d.ts +1 -14
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +11 -31
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/question/__tests__/state.test.js +349 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +6 -6
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/events.d.ts +53 -0
- package/dist/question/events.d.ts.map +1 -0
- package/dist/question/events.js +201 -0
- package/dist/question/events.js.map +1 -0
- package/dist/question/state.d.ts +30 -2
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +276 -5
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -0
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/types.js.map +1 -1
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +3 -18
- package/dist/question/ui.js.map +1 -1
- package/dist/ralph/__tests__/completion-audit.test.js +39 -0
- package/dist/ralph/__tests__/completion-audit.test.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +111 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +22 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +93 -1
- 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 +12 -6
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/run-test-files.js +12 -1
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js +25 -24
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +173 -26
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +66 -17
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +42 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +205 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/approved-execution.d.ts +13 -0
- package/dist/team/approved-execution.d.ts.map +1 -1
- package/dist/team/approved-execution.js +65 -30
- package/dist/team/approved-execution.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +28 -24
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +7 -8
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +48 -2
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/ultragoal-context.d.ts +35 -0
- package/dist/team/ultragoal-context.d.ts.map +1 -0
- package/dist/team/ultragoal-context.js +191 -0
- package/dist/team/ultragoal-context.js.map +1 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js +19 -0
- package/dist/ultragoal/__tests__/docs-contract.test.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/skills/plan/SKILL.md +3 -3
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +6 -0
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +11 -0
- package/skills/plan/SKILL.md +3 -3
- package/skills/ralph/SKILL.md +2 -2
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/team/SKILL.md +6 -0
- package/skills/ultragoal/SKILL.md +11 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +133 -1
- package/src/scripts/__tests__/run-test-files.test.ts +32 -0
- package/src/scripts/codex-native-hook.ts +121 -2
- package/src/scripts/codex-native-pre-post.ts +12 -6
- package/src/scripts/run-test-files.ts +13 -2
- package/dist/planning/__tests__/context-pack-status.test.d.ts +0 -2
- package/dist/planning/__tests__/context-pack-status.test.d.ts.map +0 -1
- package/dist/planning/__tests__/context-pack-status.test.js +0 -795
- package/dist/planning/__tests__/context-pack-status.test.js.map +0 -1
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +0 -2
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +0 -1
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +0 -612
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +0 -1
- package/dist/planning/context-pack-status.d.ts +0 -73
- package/dist/planning/context-pack-status.d.ts.map +0 -1
- package/dist/planning/context-pack-status.js +0 -745
- package/dist/planning/context-pack-status.js.map +0 -1
|
@@ -1,795 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join, relative } from 'node:path';
|
|
8
|
-
import { readContextPackHandoffStatus, resolveContextPackHandoffState, } from '../artifacts.js';
|
|
9
|
-
let tempDir;
|
|
10
|
-
function computeGitBlobSha1(content) {
|
|
11
|
-
const buffer = Buffer.from(content, 'utf-8');
|
|
12
|
-
const header = Buffer.from(`blob ${buffer.length}\0`, 'utf-8');
|
|
13
|
-
return createHash('sha1').update(header).update(buffer).digest('hex');
|
|
14
|
-
}
|
|
15
|
-
function relativeToRepo(path) {
|
|
16
|
-
return relative(tempDir, path).replaceAll('\\', '/');
|
|
17
|
-
}
|
|
18
|
-
function canonicalContextPackRelativePath(slug) {
|
|
19
|
-
return `.omx/context/context-20260507T120000Z-${slug}.json`;
|
|
20
|
-
}
|
|
21
|
-
function buildContextPackOutcome(relativePackPath) {
|
|
22
|
-
return [
|
|
23
|
-
'## Context Pack Outcome',
|
|
24
|
-
'',
|
|
25
|
-
`- pack: created \`${relativePackPath}\``,
|
|
26
|
-
].join('\n');
|
|
27
|
-
}
|
|
28
|
-
async function setup() {
|
|
29
|
-
tempDir = await mkdtemp(join(tmpdir(), 'omx-context-pack-status-'));
|
|
30
|
-
}
|
|
31
|
-
async function cleanup() {
|
|
32
|
-
if (tempDir && existsSync(tempDir)) {
|
|
33
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function writeApprovedPlan(slug, bodyLines) {
|
|
37
|
-
const plansDir = join(tempDir, '.omx', 'plans');
|
|
38
|
-
const contextDir = join(tempDir, '.omx', 'context');
|
|
39
|
-
await mkdir(plansDir, { recursive: true });
|
|
40
|
-
await mkdir(contextDir, { recursive: true });
|
|
41
|
-
const prdPath = join(plansDir, `prd-${slug}.md`);
|
|
42
|
-
const testSpecPath = join(plansDir, `test-spec-${slug}.md`);
|
|
43
|
-
const packRelativePath = canonicalContextPackRelativePath(slug);
|
|
44
|
-
const packPath = join(tempDir, packRelativePath);
|
|
45
|
-
await writeFile(prdPath, bodyLines.join('\n'));
|
|
46
|
-
await writeFile(testSpecPath, '# Test Spec\n');
|
|
47
|
-
return { prdPath, testSpecPath, packPath, packRelativePath };
|
|
48
|
-
}
|
|
49
|
-
async function writeContextPack(slug, prdPath, testSpecPath, roles) {
|
|
50
|
-
const packPath = join(tempDir, canonicalContextPackRelativePath(slug));
|
|
51
|
-
const prdActual = await readFile(prdPath, 'utf-8');
|
|
52
|
-
const testSpecActual = await readFile(testSpecPath, 'utf-8');
|
|
53
|
-
await writeFile(packPath, JSON.stringify({
|
|
54
|
-
slug,
|
|
55
|
-
basis: {
|
|
56
|
-
prd: {
|
|
57
|
-
path: relativeToRepo(prdPath),
|
|
58
|
-
sha1: computeGitBlobSha1(prdActual),
|
|
59
|
-
},
|
|
60
|
-
testSpecs: [{
|
|
61
|
-
path: relativeToRepo(testSpecPath),
|
|
62
|
-
sha1: computeGitBlobSha1(testSpecActual),
|
|
63
|
-
}],
|
|
64
|
-
},
|
|
65
|
-
entries: roles.map((role, index) => ({
|
|
66
|
-
path: `src/${role}-${index}.ts`,
|
|
67
|
-
roles: [role],
|
|
68
|
-
})),
|
|
69
|
-
}, null, 2));
|
|
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
|
-
}
|
|
90
|
-
describe('context pack handoff status', () => {
|
|
91
|
-
beforeEach(async () => { await setup(); });
|
|
92
|
-
afterEach(async () => { await cleanup(); });
|
|
93
|
-
it('maps the public context-pack state matrix into handoff status', () => {
|
|
94
|
-
assert.equal(resolveContextPackHandoffState({
|
|
95
|
-
baselineState: 'missing-prd',
|
|
96
|
-
outcomeState: 'absent',
|
|
97
|
-
packState: 'missing',
|
|
98
|
-
roleCoverage: 'unknown',
|
|
99
|
-
basisState: 'stale',
|
|
100
|
-
}), 'missing-baseline');
|
|
101
|
-
assert.equal(resolveContextPackHandoffState({
|
|
102
|
-
baselineState: 'present',
|
|
103
|
-
outcomeState: 'absent',
|
|
104
|
-
packState: 'missing',
|
|
105
|
-
roleCoverage: 'unknown',
|
|
106
|
-
basisState: 'stale',
|
|
107
|
-
}), 'plan-only');
|
|
108
|
-
assert.equal(resolveContextPackHandoffState({
|
|
109
|
-
baselineState: 'present',
|
|
110
|
-
outcomeState: 'declared',
|
|
111
|
-
packState: 'missing',
|
|
112
|
-
roleCoverage: 'unknown',
|
|
113
|
-
basisState: 'stale',
|
|
114
|
-
}), 'incomplete');
|
|
115
|
-
assert.equal(resolveContextPackHandoffState({
|
|
116
|
-
baselineState: 'present',
|
|
117
|
-
outcomeState: 'declared',
|
|
118
|
-
packState: 'valid',
|
|
119
|
-
roleCoverage: 'covered',
|
|
120
|
-
basisState: 'fresh',
|
|
121
|
-
}), 'ready');
|
|
122
|
-
assert.equal(resolveContextPackHandoffState({
|
|
123
|
-
baselineState: 'present',
|
|
124
|
-
outcomeState: 'malformed',
|
|
125
|
-
packState: 'valid',
|
|
126
|
-
roleCoverage: 'unknown',
|
|
127
|
-
basisState: 'fresh',
|
|
128
|
-
}), 'invalid');
|
|
129
|
-
assert.equal(resolveContextPackHandoffState({
|
|
130
|
-
baselineState: 'present',
|
|
131
|
-
outcomeState: 'declared',
|
|
132
|
-
packState: 'valid',
|
|
133
|
-
roleCoverage: 'covered',
|
|
134
|
-
basisState: 'stale',
|
|
135
|
-
}), 'invalid');
|
|
136
|
-
});
|
|
137
|
-
it('reports plan-only when the approved baseline has no declared context pack', async () => {
|
|
138
|
-
await writeApprovedPlan('alpha', [
|
|
139
|
-
'# PRD',
|
|
140
|
-
'',
|
|
141
|
-
'Launch via omx ralph "Execute alpha plan"',
|
|
142
|
-
]);
|
|
143
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
144
|
-
assert.equal(status.contextPack, null);
|
|
145
|
-
assert.equal(status.contextPackStatus, 'plan-only');
|
|
146
|
-
assert.equal(status.baselineState, 'present');
|
|
147
|
-
assert.equal(status.outcomeState, 'absent');
|
|
148
|
-
assert.equal(status.declarationState, 'unknown');
|
|
149
|
-
assert.equal(status.contextPackRoleRefs, null);
|
|
150
|
-
assert.equal(status.roleCoverage, 'unknown');
|
|
151
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
152
|
-
assert.deepEqual(status.contextPackIssues, []);
|
|
153
|
-
});
|
|
154
|
-
it('reports ready when the declared pack has fresh basis and all required roles', async () => {
|
|
155
|
-
const { prdPath, testSpecPath, packPath } = await writeApprovedPlan('beta', [
|
|
156
|
-
'# PRD',
|
|
157
|
-
'',
|
|
158
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('beta')),
|
|
159
|
-
'',
|
|
160
|
-
'Launch via omx ralph "Execute beta plan"',
|
|
161
|
-
]);
|
|
162
|
-
await writeContextPack('beta', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
163
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
164
|
-
assert.deepEqual(status.contextPack, { path: packPath });
|
|
165
|
-
assert.equal(status.contextPackStatus, 'ready');
|
|
166
|
-
assert.equal(status.packState, 'valid');
|
|
167
|
-
assert.equal(status.declarationState, 'matching');
|
|
168
|
-
assert.equal(status.roleCoverage, 'covered');
|
|
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
|
-
});
|
|
175
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
176
|
-
assert.deepEqual(status.contextPackIssues, []);
|
|
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
|
-
});
|
|
474
|
-
it('reports incomplete when the declared pack omits required execution roles', async () => {
|
|
475
|
-
const { prdPath, testSpecPath } = await writeApprovedPlan('gamma', [
|
|
476
|
-
'# PRD',
|
|
477
|
-
'',
|
|
478
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('gamma')),
|
|
479
|
-
'',
|
|
480
|
-
'Launch via omx ralph "Execute gamma plan"',
|
|
481
|
-
]);
|
|
482
|
-
await writeContextPack('gamma', prdPath, testSpecPath, ['scope']);
|
|
483
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
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');
|
|
488
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, ['build', 'verify']);
|
|
489
|
-
});
|
|
490
|
-
it('reports invalid when the declared pack basis drifts from the approved test spec', async () => {
|
|
491
|
-
const { prdPath, testSpecPath } = await writeApprovedPlan('delta', [
|
|
492
|
-
'# PRD',
|
|
493
|
-
'',
|
|
494
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('delta')),
|
|
495
|
-
'',
|
|
496
|
-
'Launch via omx ralph "Execute delta plan"',
|
|
497
|
-
]);
|
|
498
|
-
await writeContextPack('delta', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
499
|
-
await writeFile(testSpecPath, '# Drifted Test Spec\n');
|
|
500
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
501
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
502
|
-
assert.equal(status.roleCoverage, 'covered');
|
|
503
|
-
assert.equal(status.basisState, 'stale');
|
|
504
|
-
assert.equal(status.contextPackRoleRefs, null);
|
|
505
|
-
assert.equal(status.basisState, 'stale');
|
|
506
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
507
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
508
|
-
});
|
|
509
|
-
it('preserves inspectable missing roles even when the declared pack is otherwise invalid', async () => {
|
|
510
|
-
const { prdPath, testSpecPath } = await writeApprovedPlan('delta-missing-roles', [
|
|
511
|
-
'# PRD',
|
|
512
|
-
'',
|
|
513
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('delta-missing-roles')),
|
|
514
|
-
'',
|
|
515
|
-
'Launch via omx ralph "Execute delta missing roles plan"',
|
|
516
|
-
]);
|
|
517
|
-
await writeContextPack('delta-missing-roles', prdPath, testSpecPath, ['scope']);
|
|
518
|
-
await writeFile(testSpecPath, '# Drifted Test Spec\n');
|
|
519
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
520
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
521
|
-
assert.equal(status.roleCoverage, 'missing-required-roles');
|
|
522
|
-
assert.equal(status.contextPackRoleRefs, null);
|
|
523
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, ['build', 'verify']);
|
|
524
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
525
|
-
});
|
|
526
|
-
it('keeps role coverage unknown when the declared pack cannot be inspected', async () => {
|
|
527
|
-
const { packPath } = await writeApprovedPlan('invalid-json', [
|
|
528
|
-
'# PRD',
|
|
529
|
-
'',
|
|
530
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('invalid-json')),
|
|
531
|
-
'',
|
|
532
|
-
'Launch via omx ralph "Execute invalid json plan"',
|
|
533
|
-
]);
|
|
534
|
-
await writeFile(packPath, '{not json');
|
|
535
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
536
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
537
|
-
assert.equal(status.packState, 'invalid');
|
|
538
|
-
assert.equal(status.contextPackRoleRefs, null);
|
|
539
|
-
assert.equal(status.roleCoverage, 'unknown');
|
|
540
|
-
assert.deepEqual(status.missingRequiredContextPackRoles, []);
|
|
541
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('invalid JSON')));
|
|
542
|
-
});
|
|
543
|
-
it('ignores fenced outcome declarations and keeps the plan in plan-only status', async () => {
|
|
544
|
-
await writeApprovedPlan('epsilon', [
|
|
545
|
-
'# PRD',
|
|
546
|
-
'',
|
|
547
|
-
'```md',
|
|
548
|
-
'## Context Pack Outcome',
|
|
549
|
-
'',
|
|
550
|
-
`- pack: created \`${canonicalContextPackRelativePath('epsilon')}\``,
|
|
551
|
-
'```',
|
|
552
|
-
'',
|
|
553
|
-
'Launch via omx ralph "Execute epsilon plan"',
|
|
554
|
-
]);
|
|
555
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
556
|
-
assert.equal(status.contextPackStatus, 'plan-only');
|
|
557
|
-
assert.equal(status.outcomeState, 'absent');
|
|
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
|
-
});
|
|
650
|
-
it('rejects nested outcome paths that are not canonical context-pack files', async () => {
|
|
651
|
-
await writeApprovedPlan('eta', [
|
|
652
|
-
'# PRD',
|
|
653
|
-
'',
|
|
654
|
-
buildContextPackOutcome('.omx/context/context-20260507T120000Z-eta/nested.json'),
|
|
655
|
-
'',
|
|
656
|
-
'Launch via omx ralph "Execute eta plan"',
|
|
657
|
-
]);
|
|
658
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
659
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
660
|
-
assert.equal(status.outcomeState, 'malformed');
|
|
661
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('.omx/context/context-<timestamp>-<slug>.json')));
|
|
662
|
-
});
|
|
663
|
-
it('reports invalid when the declared pack slug does not match the approved plan even if the file is missing', async () => {
|
|
664
|
-
await writeApprovedPlan('theta', [
|
|
665
|
-
'# PRD',
|
|
666
|
-
'',
|
|
667
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('other')),
|
|
668
|
-
'',
|
|
669
|
-
'Launch via omx ralph "Execute theta plan"',
|
|
670
|
-
]);
|
|
671
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
672
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
673
|
-
assert.equal(status.outcomeState, 'declared');
|
|
674
|
-
assert.equal(status.declarationState, 'mismatched');
|
|
675
|
-
assert.equal(status.packState, 'invalid');
|
|
676
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('does not match approved plan slug theta')));
|
|
677
|
-
});
|
|
678
|
-
it('reports invalid when the approved plan declares multiple outcome sections', async () => {
|
|
679
|
-
await writeApprovedPlan('zeta', [
|
|
680
|
-
'# PRD',
|
|
681
|
-
'',
|
|
682
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('zeta')),
|
|
683
|
-
'',
|
|
684
|
-
buildContextPackOutcome(canonicalContextPackRelativePath('zeta')),
|
|
685
|
-
'',
|
|
686
|
-
'Launch via omx ralph "Execute zeta plan"',
|
|
687
|
-
]);
|
|
688
|
-
const status = readContextPackHandoffStatus(tempDir);
|
|
689
|
-
assert.equal(status.contextPackStatus, 'invalid');
|
|
690
|
-
assert.equal(status.outcomeState, 'ambiguous');
|
|
691
|
-
assert.ok(status.contextPackIssues.some((issue) => issue.includes('multiple Context Pack Outcome sections')));
|
|
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
|
-
});
|
|
794
|
-
});
|
|
795
|
-
//# sourceMappingURL=context-pack-status.test.js.map
|