oh-my-codex 0.16.2 → 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 +137 -6
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +303 -4
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +58 -0
- package/dist/cli/__tests__/launch-fallback.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 +48 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +350 -27
- 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 +269 -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 +90 -6
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +109 -19
- package/dist/cli/__tests__/update.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 +168 -16
- 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 +168 -20
- 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 +177 -43
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +54 -15
- package/dist/cli/team.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 +76 -5
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts +10 -2
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +99 -5
- package/dist/cli/update.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 +188 -4
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +129 -10
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +148 -7
- 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 +40 -4
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +204 -18
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/generator.d.ts +19 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +319 -83
- 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-fallback-watcher.test.js +29 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +10 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +1 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts +2 -0
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +176 -0
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +148 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +3 -0
- package/dist/hooks/__tests__/notify-hook-session-scope.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/__tests__/wiki-docs-contract.test.js +1 -2
- package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -1
- 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 +597 -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.d.ts +2 -0
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +1 -0
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +612 -0
- package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -0
- package/dist/planning/artifacts.d.ts +7 -2
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +279 -26
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/planning/context-pack-status.d.ts +31 -0
- package/dist/planning/context-pack-status.d.ts.map +1 -1
- package/dist/planning/context-pack-status.js +291 -25
- 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/ralph/persistence.d.ts +1 -1
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +8 -2
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +359 -24
- 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 +142 -76
- 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.d.ts +7 -0
- package/dist/scripts/notify-dispatcher.d.ts.map +1 -0
- package/dist/scripts/notify-dispatcher.js +87 -0
- package/dist/scripts/notify-dispatcher.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +4 -0
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +96 -8
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +6 -2
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.js +3 -3
- package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
- package/dist/scripts/notify-hook.js +127 -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 +84 -1
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +178 -19
- 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 +45 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/approved-execution.d.ts +1 -0
- package/dist/team/approved-execution.d.ts.map +1 -1
- package/dist/team/approved-execution.js +53 -0
- package/dist/team/approved-execution.js.map +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js +8 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +104 -18
- package/dist/team/runtime.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/mailbox.d.ts +1 -0
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +10 -1
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +5 -1
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +3 -7
- package/dist/team/state.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +7 -2
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +17 -4
- package/dist/team/worker-bootstrap.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 +389 -24
- package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
- package/src/scripts/codex-native-hook.ts +168 -64
- package/src/scripts/codex-native-pre-post.ts +4 -1
- package/src/scripts/notify-dispatcher.ts +113 -0
- package/src/scripts/notify-fallback-watcher.ts +6 -2
- package/src/scripts/notify-hook/ralph-session-resume.ts +117 -8
- package/src/scripts/notify-hook/state-io.ts +4 -2
- package/src/scripts/notify-hook/visual-verdict.ts +3 -3
- package/src/scripts/notify-hook.ts +119 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
1
|
+
import { afterEach, beforeEach, describe, it, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
4
|
+
import fs, { existsSync } from 'node:fs';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
6
7
|
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join, relative } from 'node:path';
|
|
8
|
+
import { basename, join, relative } from 'node:path';
|
|
8
9
|
import { decodeApprovedExecutionQuotedValue, isPlanningComplete, readApprovedExecutionLaunchHint, readApprovedExecutionLaunchHintOutcome, readLatestPlanningArtifacts, readPlanningArtifacts, readTeamDagArtifactResolution, } from '../artifacts.js';
|
|
9
10
|
import { readTeamDagHandoffForLatestPlan } from '../../team/dag-schema.js';
|
|
10
11
|
let tempDir;
|
|
@@ -56,6 +57,28 @@ async function writeContextPack(slug, prdPath, testSpecPath, roles) {
|
|
|
56
57
|
}, null, 2));
|
|
57
58
|
return packPath;
|
|
58
59
|
}
|
|
60
|
+
async function writeContextPackWithEntries(slug, prdPath, testSpecPath, entries) {
|
|
61
|
+
const contextDir = join(tempDir, '.omx', 'context');
|
|
62
|
+
await mkdir(contextDir, { recursive: true });
|
|
63
|
+
const packPath = join(tempDir, canonicalContextPackRelativePath(slug));
|
|
64
|
+
const prdContent = await readFile(prdPath, 'utf-8');
|
|
65
|
+
const testSpecContent = await readFile(testSpecPath, 'utf-8');
|
|
66
|
+
await writeFile(packPath, JSON.stringify({
|
|
67
|
+
slug,
|
|
68
|
+
basis: {
|
|
69
|
+
prd: {
|
|
70
|
+
path: relativeToRepo(prdPath),
|
|
71
|
+
sha1: computeGitBlobSha1(prdContent),
|
|
72
|
+
},
|
|
73
|
+
testSpecs: [{
|
|
74
|
+
path: relativeToRepo(testSpecPath),
|
|
75
|
+
sha1: computeGitBlobSha1(testSpecContent),
|
|
76
|
+
}],
|
|
77
|
+
},
|
|
78
|
+
entries,
|
|
79
|
+
}, null, 2));
|
|
80
|
+
return packPath;
|
|
81
|
+
}
|
|
59
82
|
async function setup() {
|
|
60
83
|
tempDir = await mkdtemp(join(tmpdir(), 'omx-planning-artifacts-'));
|
|
61
84
|
}
|
|
@@ -66,7 +89,11 @@ async function cleanup() {
|
|
|
66
89
|
}
|
|
67
90
|
describe('planning artifacts', () => {
|
|
68
91
|
beforeEach(async () => { await setup(); });
|
|
69
|
-
afterEach(async () => {
|
|
92
|
+
afterEach(async () => {
|
|
93
|
+
mock.restoreAll();
|
|
94
|
+
syncBuiltinESMExports();
|
|
95
|
+
await cleanup();
|
|
96
|
+
});
|
|
70
97
|
it('round-trips single-quoted approved execution tasks with only escaped apostrophes normalized', () => {
|
|
71
98
|
const task = String.raw `Fix Bob's regression in C:\\tmp`;
|
|
72
99
|
assert.equal(decodeApprovedExecutionQuotedValue(encodeApprovedExecutionTask(task, 'single')), task);
|
|
@@ -201,6 +228,7 @@ describe('planning artifacts', () => {
|
|
|
201
228
|
assert.equal(selection.prdPath, join(plansDir, 'prd-20260427T153100Z-alpha.md'));
|
|
202
229
|
assert.deepEqual(selection.testSpecPaths, []);
|
|
203
230
|
assert.equal(selection.contextPackStatus, 'missing-baseline');
|
|
231
|
+
assert.equal(selection.contextPackRoleRefs, null);
|
|
204
232
|
assert.deepEqual(selection.missingRequiredContextPackRoles, []);
|
|
205
233
|
assert.deepEqual(selection.contextPackIssues, ['Approved plan is missing a matching test spec.']);
|
|
206
234
|
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph');
|
|
@@ -209,6 +237,7 @@ describe('planning artifacts', () => {
|
|
|
209
237
|
throw new Error('expected missing-baseline approved hint outcome');
|
|
210
238
|
}
|
|
211
239
|
assert.equal(outcome.hint.contextPackStatus, 'missing-baseline');
|
|
240
|
+
assert.equal(outcome.hint.contextPackRoleRefs, null);
|
|
212
241
|
assert.deepEqual(outcome.hint.testSpecPaths, []);
|
|
213
242
|
assert.deepEqual(outcome.hint.contextPackIssues, ['Approved plan is missing a matching test spec.']);
|
|
214
243
|
assert.equal(readApprovedExecutionLaunchHint(tempDir, 'ralph'), null);
|
|
@@ -227,11 +256,13 @@ describe('planning artifacts', () => {
|
|
|
227
256
|
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
228
257
|
assert.equal(selection.contextPack, null);
|
|
229
258
|
assert.equal(selection.contextPackStatus, 'plan-only');
|
|
259
|
+
assert.equal(selection.contextPackRoleRefs, null);
|
|
230
260
|
assert.deepEqual(selection.missingRequiredContextPackRoles, []);
|
|
231
261
|
assert.deepEqual(selection.contextPackIssues, []);
|
|
232
262
|
assert.ok(hint);
|
|
233
263
|
assert.equal(hint?.contextPack, null);
|
|
234
264
|
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
265
|
+
assert.equal(hint?.contextPackRoleRefs, null);
|
|
235
266
|
assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
|
|
236
267
|
assert.deepEqual(hint?.contextPackIssues, []);
|
|
237
268
|
});
|
|
@@ -249,10 +280,129 @@ describe('planning artifacts', () => {
|
|
|
249
280
|
].join('\n'));
|
|
250
281
|
await writeFile(testSpecPath, '# Test Spec\n');
|
|
251
282
|
const packPath = await writeContextPack('context-ready', prdPath, testSpecPath, ['scope', 'build', 'verify']);
|
|
283
|
+
const selection = readLatestPlanningArtifacts(tempDir);
|
|
284
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
285
|
+
assert.deepEqual(selection.contextPack, { path: packPath });
|
|
286
|
+
assert.equal(selection.contextPackStatus, 'ready');
|
|
287
|
+
assert.deepEqual(selection.contextPackRoleRefs, {
|
|
288
|
+
scope: ['src/scope-0.ts'],
|
|
289
|
+
build: ['src/build-1.ts'],
|
|
290
|
+
verify: ['src/verify-2.ts'],
|
|
291
|
+
});
|
|
292
|
+
assert.ok(hint);
|
|
293
|
+
assert.deepEqual(hint?.contextPack, { path: packPath });
|
|
294
|
+
assert.equal(hint?.contextPackStatus, 'ready');
|
|
295
|
+
assert.deepEqual(hint?.contextPackRoleRefs, {
|
|
296
|
+
scope: ['src/scope-0.ts'],
|
|
297
|
+
build: ['src/build-1.ts'],
|
|
298
|
+
verify: ['src/verify-2.ts'],
|
|
299
|
+
});
|
|
300
|
+
assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
|
|
301
|
+
assert.deepEqual(hint?.contextPackIssues, []);
|
|
302
|
+
});
|
|
303
|
+
it('keeps approved hint role refs unchanged when ready packs carry private entry metadata', async () => {
|
|
304
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
305
|
+
await mkdir(plansDir, { recursive: true });
|
|
306
|
+
const prdPath = join(plansDir, 'prd-context-ready-private.md');
|
|
307
|
+
const testSpecPath = join(plansDir, 'test-spec-context-ready-private.md');
|
|
308
|
+
await writeFile(prdPath, [
|
|
309
|
+
'# PRD',
|
|
310
|
+
'',
|
|
311
|
+
buildContextPackOutcome(canonicalContextPackRelativePath('context-ready-private')),
|
|
312
|
+
'',
|
|
313
|
+
'Launch via omx ralph "Execute context-ready private handoff"',
|
|
314
|
+
].join('\n'));
|
|
315
|
+
await writeFile(testSpecPath, '# Test Spec\n');
|
|
316
|
+
const packPath = await writeContextPackWithEntries('context-ready-private', prdPath, testSpecPath, [
|
|
317
|
+
{
|
|
318
|
+
path: 'src/scope-ready.ts',
|
|
319
|
+
roles: ['scope'],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
path: 'src/build-ready.ts',
|
|
323
|
+
roles: ['build'],
|
|
324
|
+
label: 'Build Focus',
|
|
325
|
+
tags: ['runtime', 'build'],
|
|
326
|
+
selector: { type: 'heading', value: ' ## Build Focus ', maxWords: 120 },
|
|
327
|
+
relationPath: [
|
|
328
|
+
{ tag: 'Plan', target: ' context-ready-private ' },
|
|
329
|
+
{ tag: 'Implements', target: ' src/build-ready.ts#build-focus ' },
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
path: 'src/verify-ready.ts',
|
|
334
|
+
roles: ['verify'],
|
|
335
|
+
relationPath: [
|
|
336
|
+
{ tag: 'Plan', target: ' context-ready-private ' },
|
|
337
|
+
{ tag: 'Verifies', target: ' src/verify-ready.ts ' },
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
]);
|
|
341
|
+
const selection = readLatestPlanningArtifacts(tempDir);
|
|
342
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
343
|
+
assert.deepEqual(selection.contextPack, { path: packPath });
|
|
344
|
+
assert.equal(selection.contextPackStatus, 'ready');
|
|
345
|
+
assert.deepEqual(selection.contextPackRoleRefs, {
|
|
346
|
+
scope: ['src/scope-ready.ts'],
|
|
347
|
+
build: ['src/build-ready.ts'],
|
|
348
|
+
verify: ['src/verify-ready.ts'],
|
|
349
|
+
});
|
|
350
|
+
assert.ok(hint);
|
|
351
|
+
assert.deepEqual(hint?.contextPack, { path: packPath });
|
|
352
|
+
assert.equal(hint?.contextPackStatus, 'ready');
|
|
353
|
+
assert.deepEqual(hint?.contextPackRoleRefs, {
|
|
354
|
+
scope: ['src/scope-ready.ts'],
|
|
355
|
+
build: ['src/build-ready.ts'],
|
|
356
|
+
verify: ['src/verify-ready.ts'],
|
|
357
|
+
});
|
|
358
|
+
assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
|
|
359
|
+
assert.deepEqual(hint?.contextPackIssues, []);
|
|
360
|
+
});
|
|
361
|
+
it('keeps approved hint role refs unchanged when ready packs carry malformed private metadata', async () => {
|
|
362
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
363
|
+
await mkdir(plansDir, { recursive: true });
|
|
364
|
+
const prdPath = join(plansDir, 'prd-context-ready-private-counterfactual.md');
|
|
365
|
+
const testSpecPath = join(plansDir, 'test-spec-context-ready-private-counterfactual.md');
|
|
366
|
+
await writeFile(prdPath, [
|
|
367
|
+
'# PRD',
|
|
368
|
+
'',
|
|
369
|
+
buildContextPackOutcome(canonicalContextPackRelativePath('context-ready-private-counterfactual')),
|
|
370
|
+
'',
|
|
371
|
+
'Launch via omx ralph "Execute context-ready private counterfactual handoff"',
|
|
372
|
+
].join('\n'));
|
|
373
|
+
await writeFile(testSpecPath, '# Test Spec\n');
|
|
374
|
+
const packPath = await writeContextPackWithEntries('context-ready-private-counterfactual', prdPath, testSpecPath, [
|
|
375
|
+
{
|
|
376
|
+
path: 'src/scope-counterfactual.ts',
|
|
377
|
+
roles: ['scope'],
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
path: 'src/build-counterfactual.ts',
|
|
381
|
+
roles: ['build'],
|
|
382
|
+
selector: { type: 'heading', value: 'Build Focus', maxWords: 20 },
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
path: 'src/verify-counterfactual.ts',
|
|
386
|
+
roles: ['verify'],
|
|
387
|
+
},
|
|
388
|
+
]);
|
|
389
|
+
const selection = readLatestPlanningArtifacts(tempDir);
|
|
252
390
|
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
391
|
+
assert.deepEqual(selection.contextPack, { path: packPath });
|
|
392
|
+
assert.equal(selection.contextPackStatus, 'ready');
|
|
393
|
+
assert.deepEqual(selection.contextPackRoleRefs, {
|
|
394
|
+
scope: ['src/scope-counterfactual.ts'],
|
|
395
|
+
build: ['src/build-counterfactual.ts'],
|
|
396
|
+
verify: ['src/verify-counterfactual.ts'],
|
|
397
|
+
});
|
|
253
398
|
assert.ok(hint);
|
|
254
399
|
assert.deepEqual(hint?.contextPack, { path: packPath });
|
|
255
400
|
assert.equal(hint?.contextPackStatus, 'ready');
|
|
401
|
+
assert.deepEqual(hint?.contextPackRoleRefs, {
|
|
402
|
+
scope: ['src/scope-counterfactual.ts'],
|
|
403
|
+
build: ['src/build-counterfactual.ts'],
|
|
404
|
+
verify: ['src/verify-counterfactual.ts'],
|
|
405
|
+
});
|
|
256
406
|
assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
|
|
257
407
|
assert.deepEqual(hint?.contextPackIssues, []);
|
|
258
408
|
});
|
|
@@ -274,6 +424,7 @@ describe('planning artifacts', () => {
|
|
|
274
424
|
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
275
425
|
assert.ok(hint);
|
|
276
426
|
assert.equal(hint?.contextPackStatus, 'invalid');
|
|
427
|
+
assert.equal(hint?.contextPackRoleRefs, null);
|
|
277
428
|
assert.deepEqual(hint?.missingRequiredContextPackRoles, []);
|
|
278
429
|
assert.ok(hint?.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
279
430
|
});
|
|
@@ -295,6 +446,7 @@ describe('planning artifacts', () => {
|
|
|
295
446
|
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
296
447
|
assert.ok(hint);
|
|
297
448
|
assert.equal(hint?.contextPackStatus, 'invalid');
|
|
449
|
+
assert.equal(hint?.contextPackRoleRefs, null);
|
|
298
450
|
assert.deepEqual(hint?.missingRequiredContextPackRoles, ['build', 'verify']);
|
|
299
451
|
assert.ok(hint?.contextPackIssues.some((issue) => issue.includes('basis test-spec hash')));
|
|
300
452
|
});
|
|
@@ -418,6 +570,49 @@ describe('planning artifacts', () => {
|
|
|
418
570
|
assert.equal(hint?.sourcePath, alphaPrdPath);
|
|
419
571
|
assert.deepEqual(hint?.testSpecPaths, [join(plansDir, 'test-spec-alpha.md')]);
|
|
420
572
|
});
|
|
573
|
+
it('binds approved launch hints through canonical-equivalent requested PRD aliases', async () => {
|
|
574
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
575
|
+
await mkdir(plansDir, { recursive: true });
|
|
576
|
+
const alphaPrdPath = join(plansDir, 'prd-alpha.md');
|
|
577
|
+
await writeFile(alphaPrdPath, '# Alpha\n\nLaunch via omx ralph "Execute alpha"\n');
|
|
578
|
+
await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
|
|
579
|
+
await writeFile(join(plansDir, 'prd-zeta.md'), '# Zeta\n\nLaunch via omx ralph "Execute zeta"\n');
|
|
580
|
+
await writeFile(join(plansDir, 'test-spec-zeta.md'), '# Zeta Test Spec\n');
|
|
581
|
+
const aliases = [
|
|
582
|
+
'.omx/plans/prd-alpha.md',
|
|
583
|
+
'prd-alpha.md',
|
|
584
|
+
alphaPrdPath,
|
|
585
|
+
'.omx/plans/../plans/prd-alpha.md',
|
|
586
|
+
'../plans/prd-alpha.md',
|
|
587
|
+
join(tempDir, '.omx', 'plans', '..', 'plans', 'prd-alpha.md'),
|
|
588
|
+
];
|
|
589
|
+
for (const prdPath of aliases) {
|
|
590
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { prdPath });
|
|
591
|
+
assert.equal(outcome.status, 'resolved');
|
|
592
|
+
if (outcome.status !== 'resolved') {
|
|
593
|
+
throw new Error(`expected resolved hint for alias ${prdPath}`);
|
|
594
|
+
}
|
|
595
|
+
assert.equal(outcome.hint.task, 'Execute alpha');
|
|
596
|
+
assert.equal(outcome.hint.sourcePath, alphaPrdPath);
|
|
597
|
+
assert.deepEqual(outcome.hint.testSpecPaths, [join(plansDir, 'test-spec-alpha.md')]);
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
it('does not bind requested PRD aliases that do not resolve to a discovered canonical PRD', async () => {
|
|
601
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
602
|
+
await mkdir(plansDir, { recursive: true });
|
|
603
|
+
await writeFile(join(plansDir, 'prd-alpha.md'), '# Alpha\n\nLaunch via omx ralph "Execute alpha"\n');
|
|
604
|
+
await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
|
|
605
|
+
const rejectedAliases = [
|
|
606
|
+
'.omx/plans/prd-missing.md',
|
|
607
|
+
'../prd-alpha.md',
|
|
608
|
+
join('..', basename(tempDir), '.omx', 'plans', 'prd-alpha.md'),
|
|
609
|
+
join(tempDir, '.omx', 'prd-alpha.md'),
|
|
610
|
+
relative(process.cwd(), join(plansDir, 'prd-alpha.md')),
|
|
611
|
+
];
|
|
612
|
+
for (const prdPath of rejectedAliases) {
|
|
613
|
+
assert.equal(readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { prdPath }).status, 'absent');
|
|
614
|
+
}
|
|
615
|
+
});
|
|
421
616
|
it('honors the requested Ralph task when a single plan lists multiple Ralph launch hints', async () => {
|
|
422
617
|
const plansDir = join(tempDir, '.omx', 'plans');
|
|
423
618
|
await mkdir(plansDir, { recursive: true });
|
|
@@ -433,6 +628,30 @@ describe('planning artifacts', () => {
|
|
|
433
628
|
assert.equal(hint?.task, 'Execute alpha');
|
|
434
629
|
assert.equal(hint?.command, 'omx ralph "Execute alpha"');
|
|
435
630
|
});
|
|
631
|
+
it('reuses one planning artifact scan while task lookup checks older same-lineage PRDs', async () => {
|
|
632
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
633
|
+
const task = 'Execute cached planning artifact lineage lookup';
|
|
634
|
+
await mkdir(plansDir, { recursive: true });
|
|
635
|
+
await writeFile(join(plansDir, 'prd-alpha.md'), `# Alpha\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
|
|
636
|
+
await writeFile(join(plansDir, 'test-spec-alpha.md'), '# Alpha Test Spec\n');
|
|
637
|
+
await writeFile(join(plansDir, 'prd-beta.md'), `# Beta\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
|
|
638
|
+
await writeFile(join(plansDir, 'prd-gamma.md'), `# Gamma\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
|
|
639
|
+
await writeFile(join(plansDir, 'prd-zeta.md'), `# Zeta\n\nLaunch via omx ralph ${JSON.stringify(task)}\n`);
|
|
640
|
+
let readdirCount = 0;
|
|
641
|
+
const originalReaddirSync = fs.readdirSync;
|
|
642
|
+
mock.method(fs, 'readdirSync', ((...args) => {
|
|
643
|
+
readdirCount += 1;
|
|
644
|
+
return Reflect.apply(originalReaddirSync, fs, args);
|
|
645
|
+
}));
|
|
646
|
+
syncBuiltinESMExports();
|
|
647
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
|
|
648
|
+
assert.equal(outcome.status, 'resolved');
|
|
649
|
+
if (outcome.status !== 'resolved') {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
assert.equal(outcome.hint.sourcePath, join(plansDir, 'prd-alpha.md'));
|
|
653
|
+
assert.equal(readdirCount, 2);
|
|
654
|
+
});
|
|
436
655
|
it('fails closed for bare Ralph lookups when a single plan lists multiple Ralph launch hints', async () => {
|
|
437
656
|
const plansDir = join(tempDir, '.omx', 'plans');
|
|
438
657
|
await mkdir(plansDir, { recursive: true });
|
|
@@ -446,6 +665,335 @@ describe('planning artifacts', () => {
|
|
|
446
665
|
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
447
666
|
assert.equal(hint, null);
|
|
448
667
|
});
|
|
668
|
+
it('ignores Ralph launch hints that appear only inside indented code blocks', async () => {
|
|
669
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
670
|
+
const task = 'Execute hidden indented ralph plan';
|
|
671
|
+
await mkdir(plansDir, { recursive: true });
|
|
672
|
+
await writeFile(join(plansDir, 'prd-hidden-indented-ralph.md'), [
|
|
673
|
+
'# PRD',
|
|
674
|
+
'',
|
|
675
|
+
` omx ralph ${JSON.stringify(task)}`,
|
|
676
|
+
].join('\n'));
|
|
677
|
+
await writeFile(join(plansDir, 'test-spec-hidden-indented-ralph.md'), '# Test Spec\n');
|
|
678
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
|
|
679
|
+
assert.equal(outcome.status, 'absent');
|
|
680
|
+
});
|
|
681
|
+
it('resolves Ralph launch hints wrapped across visible markdown lines', async () => {
|
|
682
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
683
|
+
const task = 'Execute wrapped visible ralph plan';
|
|
684
|
+
const command = `omx ralph ${JSON.stringify(task)}`;
|
|
685
|
+
await mkdir(plansDir, { recursive: true });
|
|
686
|
+
await writeFile(join(plansDir, 'prd-wrapped-visible-ralph.md'), [
|
|
687
|
+
'# PRD',
|
|
688
|
+
'',
|
|
689
|
+
'Launch via omx ralph',
|
|
690
|
+
JSON.stringify(task),
|
|
691
|
+
].join('\n'));
|
|
692
|
+
await writeFile(join(plansDir, 'test-spec-wrapped-visible-ralph.md'), '# Test Spec\n');
|
|
693
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph', { task });
|
|
694
|
+
assert.ok(hint);
|
|
695
|
+
assert.equal(hint?.task, task);
|
|
696
|
+
assert.equal(hint?.command, command);
|
|
697
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
698
|
+
const exactHint = readApprovedExecutionLaunchHint(tempDir, 'ralph', { command });
|
|
699
|
+
assert.ok(exactHint);
|
|
700
|
+
assert.equal(exactHint?.command, command);
|
|
701
|
+
});
|
|
702
|
+
it('does not let Ralph launch hints span hidden markdown gaps', async () => {
|
|
703
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
704
|
+
await mkdir(plansDir, { recursive: true });
|
|
705
|
+
const scenarios = [
|
|
706
|
+
{
|
|
707
|
+
name: 'fenced',
|
|
708
|
+
buildHiddenLines: (task) => [
|
|
709
|
+
'```md',
|
|
710
|
+
JSON.stringify(task),
|
|
711
|
+
'```',
|
|
712
|
+
],
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: 'commented',
|
|
716
|
+
buildHiddenLines: (task) => [
|
|
717
|
+
'<!--',
|
|
718
|
+
JSON.stringify(task),
|
|
719
|
+
'-->',
|
|
720
|
+
],
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: 'indented',
|
|
724
|
+
buildHiddenLines: (task) => [
|
|
725
|
+
` ${JSON.stringify(task)}`,
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
];
|
|
729
|
+
for (const scenario of scenarios) {
|
|
730
|
+
const task = `Execute hidden-gap ${scenario.name} ralph plan`;
|
|
731
|
+
await writeFile(join(plansDir, `prd-hidden-gap-${scenario.name}-ralph.md`), [
|
|
732
|
+
'# PRD',
|
|
733
|
+
'',
|
|
734
|
+
'Launch via omx ralph',
|
|
735
|
+
...scenario.buildHiddenLines(task),
|
|
736
|
+
].join('\n'));
|
|
737
|
+
await writeFile(join(plansDir, `test-spec-hidden-gap-${scenario.name}-ralph.md`), '# Test Spec\n');
|
|
738
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
|
|
739
|
+
assert.equal(outcome.status, 'absent', scenario.name);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
it('resolves Team launch hints wrapped across visible markdown lines', async () => {
|
|
743
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
744
|
+
const task = 'Execute wrapped visible team plan';
|
|
745
|
+
const command = `omx team 2:executor ${JSON.stringify(task)}`;
|
|
746
|
+
await mkdir(plansDir, { recursive: true });
|
|
747
|
+
await writeFile(join(plansDir, 'prd-wrapped-visible-team.md'), [
|
|
748
|
+
'# PRD',
|
|
749
|
+
'',
|
|
750
|
+
'Launch via omx team',
|
|
751
|
+
'2:executor',
|
|
752
|
+
JSON.stringify(task),
|
|
753
|
+
].join('\n'));
|
|
754
|
+
await writeFile(join(plansDir, 'test-spec-wrapped-visible-team.md'), '# Test Spec\n');
|
|
755
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'team', { task });
|
|
756
|
+
assert.ok(hint);
|
|
757
|
+
assert.equal(hint?.task, task);
|
|
758
|
+
assert.equal(hint?.workerCount, 2);
|
|
759
|
+
assert.equal(hint?.agentType, 'executor');
|
|
760
|
+
assert.equal(hint?.command, command);
|
|
761
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
762
|
+
const exactHint = readApprovedExecutionLaunchHint(tempDir, 'team', { command });
|
|
763
|
+
assert.ok(exactHint);
|
|
764
|
+
assert.equal(exactHint?.command, command);
|
|
765
|
+
});
|
|
766
|
+
it('normalizes wrapped linked-Ralph team launch hints for exact command matching', async () => {
|
|
767
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
768
|
+
const task = 'Execute wrapped linked ralph team plan';
|
|
769
|
+
const command = `$team ralph 5:debugger ${JSON.stringify(task)}`;
|
|
770
|
+
await mkdir(plansDir, { recursive: true });
|
|
771
|
+
await writeFile(join(plansDir, 'prd-wrapped-visible-team-linked-ralph.md'), [
|
|
772
|
+
'# PRD',
|
|
773
|
+
'',
|
|
774
|
+
'Launch via $team',
|
|
775
|
+
'ralph',
|
|
776
|
+
'5:debugger',
|
|
777
|
+
JSON.stringify(task),
|
|
778
|
+
].join('\n'));
|
|
779
|
+
await writeFile(join(plansDir, 'test-spec-wrapped-visible-team-linked-ralph.md'), '# Test Spec\n');
|
|
780
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'team', { command });
|
|
781
|
+
assert.ok(hint);
|
|
782
|
+
assert.equal(hint?.command, command);
|
|
783
|
+
assert.equal(hint?.workerCount, 5);
|
|
784
|
+
assert.equal(hint?.agentType, 'debugger');
|
|
785
|
+
assert.equal(hint?.linkedRalph, true);
|
|
786
|
+
});
|
|
787
|
+
it('keeps exact-command normalization bounded to visible whitespace-only variants', async () => {
|
|
788
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
789
|
+
await mkdir(plansDir, { recursive: true });
|
|
790
|
+
const task = 'Execute exact-command normalization state table plan';
|
|
791
|
+
const command = `omx team 2:executor ${JSON.stringify(task)}`;
|
|
792
|
+
const cases = [
|
|
793
|
+
{
|
|
794
|
+
name: 'visible-whitespace-only-variant',
|
|
795
|
+
prdPath: 'prd-exact-command-visible.md',
|
|
796
|
+
content: [
|
|
797
|
+
'# PRD',
|
|
798
|
+
'',
|
|
799
|
+
'Launch via omx team',
|
|
800
|
+
'2:executor',
|
|
801
|
+
JSON.stringify(task),
|
|
802
|
+
].join('\n'),
|
|
803
|
+
expectedStatus: 'resolved',
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
name: 'hidden-gap-counterfactual',
|
|
807
|
+
prdPath: 'prd-exact-command-hidden-gap.md',
|
|
808
|
+
content: [
|
|
809
|
+
'# PRD',
|
|
810
|
+
'',
|
|
811
|
+
'Launch via omx team',
|
|
812
|
+
'```md',
|
|
813
|
+
'hidden',
|
|
814
|
+
'```',
|
|
815
|
+
'2:executor',
|
|
816
|
+
JSON.stringify(task),
|
|
817
|
+
].join('\n'),
|
|
818
|
+
expectedStatus: 'absent',
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
name: 'formatting-only-duplicate',
|
|
822
|
+
prdPath: 'prd-exact-command-duplicate.md',
|
|
823
|
+
content: [
|
|
824
|
+
'# PRD',
|
|
825
|
+
'',
|
|
826
|
+
`Launch via ${command}`,
|
|
827
|
+
'Launch via omx team',
|
|
828
|
+
'2:executor',
|
|
829
|
+
JSON.stringify(task),
|
|
830
|
+
].join('\n'),
|
|
831
|
+
expectedStatus: 'ambiguous',
|
|
832
|
+
},
|
|
833
|
+
];
|
|
834
|
+
for (const scenario of cases) {
|
|
835
|
+
await writeFile(join(plansDir, scenario.prdPath), scenario.content);
|
|
836
|
+
await writeFile(join(plansDir, scenario.prdPath.replace(/^prd-/, 'test-spec-')), '# Test Spec\n');
|
|
837
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', { command });
|
|
838
|
+
assert.equal(outcome.status, scenario.expectedStatus, scenario.name);
|
|
839
|
+
await rm(join(plansDir, scenario.prdPath), { force: true });
|
|
840
|
+
await rm(join(plansDir, scenario.prdPath.replace(/^prd-/, 'test-spec-')), { force: true });
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
it('does not normalize whitespace that changes the quoted task payload', async () => {
|
|
844
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
845
|
+
await mkdir(plansDir, { recursive: true });
|
|
846
|
+
const task = 'Execute embedded\nnewline task';
|
|
847
|
+
const exactCommand = [
|
|
848
|
+
'omx ralph "Execute embedded',
|
|
849
|
+
'newline task"',
|
|
850
|
+
].join('\n');
|
|
851
|
+
const collapsedCommand = 'omx ralph "Execute embedded newline task"';
|
|
852
|
+
await writeFile(join(plansDir, 'prd-exact-command-embedded-newline-task.md'), [
|
|
853
|
+
'# PRD',
|
|
854
|
+
'',
|
|
855
|
+
'Launch via omx ralph "Execute embedded',
|
|
856
|
+
'newline task"',
|
|
857
|
+
].join('\n'));
|
|
858
|
+
await writeFile(join(plansDir, 'test-spec-exact-command-embedded-newline-task.md'), '# Test Spec\n');
|
|
859
|
+
const exactOutcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { command: exactCommand });
|
|
860
|
+
assert.equal(exactOutcome.status, 'resolved');
|
|
861
|
+
if (exactOutcome.status !== 'resolved') {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
assert.equal(exactOutcome.hint.command, exactCommand);
|
|
865
|
+
assert.equal(exactOutcome.hint.task, task);
|
|
866
|
+
assert.equal(readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { command: collapsedCommand }).status, 'absent');
|
|
867
|
+
});
|
|
868
|
+
it('ignores Team launch hints inside fenced code blocks', async () => {
|
|
869
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
870
|
+
const task = 'Execute approved issue 1314 fenced team plan';
|
|
871
|
+
await mkdir(plansDir, { recursive: true });
|
|
872
|
+
await writeFile(join(plansDir, 'prd-issue-1314-fenced-team.md'), [
|
|
873
|
+
'# PRD',
|
|
874
|
+
'',
|
|
875
|
+
'```sh',
|
|
876
|
+
`omx team 5:reviewer ${JSON.stringify(task)}`,
|
|
877
|
+
'```',
|
|
878
|
+
'',
|
|
879
|
+
`Launch via omx team 2:executor ${JSON.stringify(task)}`,
|
|
880
|
+
].join('\n'));
|
|
881
|
+
await writeFile(join(plansDir, 'test-spec-issue-1314-fenced-team.md'), '# Test Spec\n');
|
|
882
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
|
|
883
|
+
assert.ok(hint);
|
|
884
|
+
assert.equal(hint?.task, task);
|
|
885
|
+
assert.equal(hint?.workerCount, 2);
|
|
886
|
+
assert.equal(hint?.agentType, 'executor');
|
|
887
|
+
assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
|
|
888
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
889
|
+
});
|
|
890
|
+
it('ignores Team launch hints that appear only inside fenced code blocks', async () => {
|
|
891
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
892
|
+
const task = 'Execute hidden fenced team plan';
|
|
893
|
+
await mkdir(plansDir, { recursive: true });
|
|
894
|
+
await writeFile(join(plansDir, 'prd-hidden-fenced-team.md'), [
|
|
895
|
+
'# PRD',
|
|
896
|
+
'',
|
|
897
|
+
'```sh',
|
|
898
|
+
`omx team 2:executor ${JSON.stringify(task)}`,
|
|
899
|
+
'```',
|
|
900
|
+
].join('\n'));
|
|
901
|
+
await writeFile(join(plansDir, 'test-spec-hidden-fenced-team.md'), '# Test Spec\n');
|
|
902
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', { task });
|
|
903
|
+
assert.equal(outcome.status, 'absent');
|
|
904
|
+
});
|
|
905
|
+
it('ignores adversarial hidden Team launch hints that try to break out with deceptive fence lines', async () => {
|
|
906
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
907
|
+
const task = 'Execute adversarial fenced team plan';
|
|
908
|
+
await mkdir(plansDir, { recursive: true });
|
|
909
|
+
await writeFile(join(plansDir, 'prd-adversarial-fenced-team.md'), [
|
|
910
|
+
'# PRD',
|
|
911
|
+
'',
|
|
912
|
+
'```md',
|
|
913
|
+
`omx team 9:reviewer ${JSON.stringify(task)}`,
|
|
914
|
+
' ```',
|
|
915
|
+
'```still-open',
|
|
916
|
+
'~~~',
|
|
917
|
+
`omx team 7:critic ${JSON.stringify(task)}`,
|
|
918
|
+
'```',
|
|
919
|
+
'',
|
|
920
|
+
`Launch via omx team 2:executor ${JSON.stringify(task)}`,
|
|
921
|
+
].join('\n'));
|
|
922
|
+
await writeFile(join(plansDir, 'test-spec-adversarial-fenced-team.md'), '# Test Spec\n');
|
|
923
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
|
|
924
|
+
assert.ok(hint);
|
|
925
|
+
assert.equal(hint?.task, task);
|
|
926
|
+
assert.equal(hint?.workerCount, 2);
|
|
927
|
+
assert.equal(hint?.agentType, 'executor');
|
|
928
|
+
assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
|
|
929
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
930
|
+
});
|
|
931
|
+
it('ignores Team launch hints inside nested commented blocks with inner fence lookalikes', async () => {
|
|
932
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
933
|
+
const task = 'Execute commented nested team plan';
|
|
934
|
+
await mkdir(plansDir, { recursive: true });
|
|
935
|
+
await writeFile(join(plansDir, 'prd-commented-nested-team.md'), [
|
|
936
|
+
'# PRD',
|
|
937
|
+
'',
|
|
938
|
+
'<!--',
|
|
939
|
+
`Launch via omx team 9:reviewer ${JSON.stringify(task)}`,
|
|
940
|
+
'<!--',
|
|
941
|
+
`omx team 7:critic ${JSON.stringify(task)}`,
|
|
942
|
+
'```md',
|
|
943
|
+
`Launch via omx team 6:debugger ${JSON.stringify(task)}`,
|
|
944
|
+
'-->',
|
|
945
|
+
'~~~',
|
|
946
|
+
'-->',
|
|
947
|
+
'',
|
|
948
|
+
`Launch via omx team 2:executor ${JSON.stringify(task)}`,
|
|
949
|
+
].join('\n'));
|
|
950
|
+
await writeFile(join(plansDir, 'test-spec-commented-nested-team.md'), '# Test Spec\n');
|
|
951
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'team');
|
|
952
|
+
assert.ok(hint);
|
|
953
|
+
assert.equal(hint?.task, task);
|
|
954
|
+
assert.equal(hint?.workerCount, 2);
|
|
955
|
+
assert.equal(hint?.agentType, 'executor');
|
|
956
|
+
assert.equal(hint?.command, `omx team 2:executor ${JSON.stringify(task)}`);
|
|
957
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
958
|
+
});
|
|
959
|
+
it('ignores Ralph launch hints inside indented code blocks', async () => {
|
|
960
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
961
|
+
const task = 'Execute approved issue 1314 indented ralph plan';
|
|
962
|
+
await mkdir(plansDir, { recursive: true });
|
|
963
|
+
await writeFile(join(plansDir, 'prd-issue-1314-indented-ralph.md'), [
|
|
964
|
+
'# PRD',
|
|
965
|
+
'',
|
|
966
|
+
` omx ralph ${JSON.stringify(task)}`,
|
|
967
|
+
'',
|
|
968
|
+
`Launch via omx ralph ${JSON.stringify(task)}`,
|
|
969
|
+
].join('\n'));
|
|
970
|
+
await writeFile(join(plansDir, 'test-spec-issue-1314-indented-ralph.md'), '# Test Spec\n');
|
|
971
|
+
const hint = readApprovedExecutionLaunchHint(tempDir, 'ralph');
|
|
972
|
+
assert.ok(hint);
|
|
973
|
+
assert.equal(hint?.task, task);
|
|
974
|
+
assert.equal(hint?.command, `omx ralph ${JSON.stringify(task)}`);
|
|
975
|
+
assert.equal(hint?.contextPackStatus, 'plan-only');
|
|
976
|
+
});
|
|
977
|
+
it('ignores Ralph launch hints that appear only inside nested commented blocks', async () => {
|
|
978
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
979
|
+
const task = 'Execute hidden commented ralph plan';
|
|
980
|
+
await mkdir(plansDir, { recursive: true });
|
|
981
|
+
await writeFile(join(plansDir, 'prd-hidden-commented-ralph.md'), [
|
|
982
|
+
'# PRD',
|
|
983
|
+
'',
|
|
984
|
+
'<!--',
|
|
985
|
+
`Launch via omx ralph ${JSON.stringify(task)}`,
|
|
986
|
+
'<!--',
|
|
987
|
+
`omx ralph ${JSON.stringify(task)}`,
|
|
988
|
+
'```md',
|
|
989
|
+
`Launch via omx ralph ${JSON.stringify(task)}`,
|
|
990
|
+
'-->',
|
|
991
|
+
'-->',
|
|
992
|
+
].join('\n'));
|
|
993
|
+
await writeFile(join(plansDir, 'test-spec-hidden-commented-ralph.md'), '# Test Spec\n');
|
|
994
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'ralph', { task });
|
|
995
|
+
assert.equal(outcome.status, 'absent');
|
|
996
|
+
});
|
|
449
997
|
it('honors the requested team task when a single plan lists multiple team launch hints', async () => {
|
|
450
998
|
const plansDir = join(tempDir, '.omx', 'plans');
|
|
451
999
|
await mkdir(plansDir, { recursive: true });
|
|
@@ -480,6 +1028,51 @@ describe('planning artifacts', () => {
|
|
|
480
1028
|
});
|
|
481
1029
|
assert.equal(hint, null);
|
|
482
1030
|
});
|
|
1031
|
+
it('uses the requested team launch signature to disambiguate same-task launch hints', async () => {
|
|
1032
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
1033
|
+
const sharedTask = 'Execute shared team handoff';
|
|
1034
|
+
await mkdir(plansDir, { recursive: true });
|
|
1035
|
+
await writeFile(join(plansDir, 'prd-issue-910-signature.md'), [
|
|
1036
|
+
'# PRD',
|
|
1037
|
+
'',
|
|
1038
|
+
`Launch via omx team 2:executor ${JSON.stringify(sharedTask)}`,
|
|
1039
|
+
`Launch via $team ralph 5:debugger ${JSON.stringify(sharedTask)}`,
|
|
1040
|
+
].join('\n'));
|
|
1041
|
+
await writeFile(join(plansDir, 'test-spec-issue-910-signature.md'), '# Test Spec\n');
|
|
1042
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', {
|
|
1043
|
+
task: sharedTask,
|
|
1044
|
+
workerCount: 2,
|
|
1045
|
+
agentType: 'executor',
|
|
1046
|
+
linkedRalph: false,
|
|
1047
|
+
});
|
|
1048
|
+
assert.equal(outcome.status, 'resolved');
|
|
1049
|
+
if (outcome.status !== 'resolved') {
|
|
1050
|
+
throw new Error('expected a resolved team launch-hint outcome');
|
|
1051
|
+
}
|
|
1052
|
+
assert.equal(outcome.hint.command, `omx team 2:executor ${JSON.stringify(sharedTask)}`);
|
|
1053
|
+
assert.equal(outcome.hint.workerCount, 2);
|
|
1054
|
+
assert.equal(outcome.hint.agentType, 'executor');
|
|
1055
|
+
assert.equal(outcome.hint.linkedRalph, false);
|
|
1056
|
+
});
|
|
1057
|
+
it('keeps same-task team launch-hint selection ambiguous when the full signature repeats', async () => {
|
|
1058
|
+
const plansDir = join(tempDir, '.omx', 'plans');
|
|
1059
|
+
const sharedTask = 'Execute shared duplicate team handoff';
|
|
1060
|
+
await mkdir(plansDir, { recursive: true });
|
|
1061
|
+
await writeFile(join(plansDir, 'prd-issue-910-same-signature.md'), [
|
|
1062
|
+
'# PRD',
|
|
1063
|
+
'',
|
|
1064
|
+
`Launch via omx team 2:executor ${JSON.stringify(sharedTask)}`,
|
|
1065
|
+
`Launch via $team 2:executor ${JSON.stringify(sharedTask)}`,
|
|
1066
|
+
].join('\n'));
|
|
1067
|
+
await writeFile(join(plansDir, 'test-spec-issue-910-same-signature.md'), '# Test Spec\n');
|
|
1068
|
+
const outcome = readApprovedExecutionLaunchHintOutcome(tempDir, 'team', {
|
|
1069
|
+
task: sharedTask,
|
|
1070
|
+
workerCount: 2,
|
|
1071
|
+
agentType: 'executor',
|
|
1072
|
+
linkedRalph: false,
|
|
1073
|
+
});
|
|
1074
|
+
assert.equal(outcome.status, 'ambiguous');
|
|
1075
|
+
});
|
|
483
1076
|
it('rehydrates the exact team launch hint by command when one PRD repeats the same task', async () => {
|
|
484
1077
|
const plansDir = join(tempDir, '.omx', 'plans');
|
|
485
1078
|
const sharedTask = 'Ship feature';
|