oh-my-codex 0.13.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js +91 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js.map +1 -0
- package/dist/autoresearch/skill-validation.d.ts +13 -0
- package/dist/autoresearch/skill-validation.d.ts.map +1 -0
- package/dist/autoresearch/skill-validation.js +165 -0
- package/dist/autoresearch/skill-validation.js.map +1 -0
- package/dist/catalog/__tests__/schema.test.js +6 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js +236 -273
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +64 -653
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +7 -0
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +2 -1
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.d.ts +2 -0
- package/dist/cli/__tests__/question.test.d.ts.map +1 -0
- package/dist/cli/__tests__/question.test.js +113 -0
- package/dist/cli/__tests__/question.test.js.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.js +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -0
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts +24 -7
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +189 -130
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +3 -2
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +29 -305
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +43 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +8 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts +3 -0
- package/dist/cli/question.d.ts.map +1 -0
- package/dist/cli/question.js +182 -0
- package/dist/cli/question.js.map +1 -0
- package/dist/hooks/__tests__/analyze-routing-contract.test.js +22 -13
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +2 -2
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +22 -5
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +2 -2
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +308 -17
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +570 -2
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +717 -16
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +25 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +894 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +34 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +132 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +22 -4
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js +4 -2
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts +1 -0
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +4 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +28 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js +5 -4
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-team-routing.test.js +2 -2
- package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/triage-config.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-config.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-config.test.js +211 -0
- package/dist/hooks/__tests__/triage-config.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js +230 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.js +426 -0
- package/dist/hooks/__tests__/triage-state.test.js.map +1 -0
- package/dist/hooks/keyword-detector.d.ts +26 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +97 -26
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +16 -9
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +28 -1
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/triage-config.d.ts +33 -0
- package/dist/hooks/triage-config.d.ts.map +1 -0
- package/dist/hooks/triage-config.js +87 -0
- package/dist/hooks/triage-config.js.map +1 -0
- package/dist/hooks/triage-heuristic.d.ts +20 -0
- package/dist/hooks/triage-heuristic.d.ts.map +1 -0
- package/dist/hooks/triage-heuristic.js +210 -0
- package/dist/hooks/triage-heuristic.js.map +1 -0
- package/dist/hooks/triage-state.d.ts +63 -0
- package/dist/hooks/triage-state.d.ts.map +1 -0
- package/dist/hooks/triage-state.js +138 -0
- package/dist/hooks/triage-state.js.map +1 -0
- package/dist/hud/__tests__/reconcile.test.js +20 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +1 -0
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +2 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +1 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +8 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +4 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-ralph-contract.test.js +15 -0
- package/dist/modes/__tests__/base-ralph-contract.test.js.map +1 -1
- package/dist/modes/base.d.ts +1 -0
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +22 -6
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/index.test.js +78 -0
- package/dist/notifications/__tests__/index.test.js.map +1 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +39 -22
- package/dist/notifications/index.js.map +1 -1
- package/dist/openclaw/index.d.ts +5 -3
- package/dist/openclaw/index.d.ts.map +1 -1
- package/dist/openclaw/index.js +5 -3
- package/dist/openclaw/index.js.map +1 -1
- package/dist/question/__tests__/client.test.d.ts +2 -0
- package/dist/question/__tests__/client.test.d.ts.map +1 -0
- package/dist/question/__tests__/client.test.js +70 -0
- package/dist/question/__tests__/client.test.js.map +1 -0
- package/dist/question/__tests__/deep-interview.test.d.ts +2 -0
- package/dist/question/__tests__/deep-interview.test.d.ts.map +1 -0
- package/dist/question/__tests__/deep-interview.test.js +108 -0
- package/dist/question/__tests__/deep-interview.test.js.map +1 -0
- package/dist/question/__tests__/policy.test.d.ts +2 -0
- package/dist/question/__tests__/policy.test.d.ts.map +1 -0
- package/dist/question/__tests__/policy.test.js +107 -0
- package/dist/question/__tests__/policy.test.js.map +1 -0
- package/dist/question/__tests__/renderer.test.d.ts +2 -0
- package/dist/question/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/question/__tests__/renderer.test.js +88 -0
- package/dist/question/__tests__/renderer.test.js.map +1 -0
- package/dist/question/__tests__/state.test.d.ts +2 -0
- package/dist/question/__tests__/state.test.d.ts.map +1 -0
- package/dist/question/__tests__/state.test.js +55 -0
- package/dist/question/__tests__/state.test.js.map +1 -0
- package/dist/question/__tests__/types.test.d.ts +2 -0
- package/dist/question/__tests__/types.test.d.ts.map +1 -0
- package/dist/question/__tests__/types.test.js +44 -0
- package/dist/question/__tests__/types.test.js.map +1 -0
- package/dist/question/__tests__/ui.test.d.ts +2 -0
- package/dist/question/__tests__/ui.test.d.ts.map +1 -0
- package/dist/question/__tests__/ui.test.js +169 -0
- package/dist/question/__tests__/ui.test.js.map +1 -0
- package/dist/question/client.d.ts +54 -0
- package/dist/question/client.d.ts.map +1 -0
- package/dist/question/client.js +77 -0
- package/dist/question/client.js.map +1 -0
- package/dist/question/deep-interview.d.ts +27 -0
- package/dist/question/deep-interview.d.ts.map +1 -0
- package/dist/question/deep-interview.js +101 -0
- package/dist/question/deep-interview.js.map +1 -0
- package/dist/question/policy.d.ts +18 -0
- package/dist/question/policy.d.ts.map +1 -0
- package/dist/question/policy.js +77 -0
- package/dist/question/policy.js.map +1 -0
- package/dist/question/renderer.d.ts +18 -0
- package/dist/question/renderer.d.ts.map +1 -0
- package/dist/question/renderer.js +128 -0
- package/dist/question/renderer.js.map +1 -0
- package/dist/question/state.d.ts +19 -0
- package/dist/question/state.d.ts.map +1 -0
- package/dist/question/state.js +108 -0
- package/dist/question/state.js.map +1 -0
- package/dist/question/types.d.ts +66 -0
- package/dist/question/types.d.ts.map +1 -0
- package/dist/question/types.js +82 -0
- package/dist/question/types.js.map +1 -0
- package/dist/question/ui.d.ts +38 -0
- package/dist/question/ui.d.ts.map +1 -0
- package/dist/question/ui.js +321 -0
- package/dist/question/ui.js.map +1 -0
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralph/contract.js +4 -1
- package/dist/ralph/contract.js.map +1 -1
- package/dist/ralplan/runtime.js +1 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/__tests__/run-loop.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-loop.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-loop.test.js +35 -0
- package/dist/runtime/__tests__/run-loop.test.js.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.js +64 -0
- package/dist/runtime/__tests__/run-outcome.test.js.map +1 -0
- package/dist/runtime/run-loop.d.ts +41 -0
- package/dist/runtime/run-loop.d.ts.map +1 -0
- package/dist/runtime/run-loop.js +46 -0
- package/dist/runtime/run-loop.js.map +1 -0
- package/dist/runtime/run-outcome.d.ts +28 -0
- package/dist/runtime/run-outcome.d.ts.map +1 -0
- package/dist/runtime/run-outcome.js +136 -0
- package/dist/runtime/run-outcome.js.map +1 -0
- package/dist/runtime/run-state.d.ts +36 -0
- package/dist/runtime/run-state.d.ts.map +1 -0
- package/dist/runtime/run-state.js +110 -0
- package/dist/runtime/run-state.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +1128 -85
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +2 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +199 -11
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +81 -2
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +27 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +83 -20
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +64 -38
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook.js +15 -5
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/sync-prompt-guidance-fragments.js +5 -0
- package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +21 -0
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +11 -0
- 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 +15 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +14 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +3 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +15 -0
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js +41 -0
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/followup-planner.d.ts.map +1 -1
- package/dist/team/followup-planner.js +31 -9
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/role-router.d.ts.map +1 -1
- package/dist/team/role-router.js +73 -0
- package/dist/team/role-router.js.map +1 -1
- package/package.json +3 -2
- package/prompts/dependency-expert.md +3 -0
- package/prompts/executor.md +5 -0
- package/prompts/explore.md +2 -0
- package/prompts/planner.md +5 -0
- package/prompts/product-analyst.md +8 -8
- package/prompts/researcher.md +78 -30
- package/prompts/verifier.md +4 -0
- package/skills/autoresearch/SKILL.md +68 -0
- package/skills/deep-interview/SKILL.md +10 -9
- package/skills/help/SKILL.md +3 -1
- package/skills/ralplan/SKILL.md +1 -0
- package/skills/team/SKILL.md +1 -0
- package/skills/ultrawork/SKILL.md +1 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +1495 -188
- package/src/scripts/codex-native-hook.ts +235 -19
- package/src/scripts/notify-fallback-watcher.ts +92 -2
- package/src/scripts/notify-hook/auto-nudge.ts +89 -20
- package/src/scripts/notify-hook/managed-tmux.ts +70 -31
- package/src/scripts/notify-hook/ralph-session-resume.ts +1 -1
- package/src/scripts/notify-hook.ts +23 -5
- package/src/scripts/sync-prompt-guidance-fragments.ts +4 -0
- package/templates/AGENTS.md +48 -37
- package/templates/catalog-manifest.json +7 -0
|
@@ -4,7 +4,7 @@ import { existsSync } from 'node:fs';
|
|
|
4
4
|
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, } from '../keyword-detector.js';
|
|
7
|
+
import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
|
|
8
8
|
import { SKILL_ACTIVE_STATE_FILE } from '../../state/skill-active.js';
|
|
9
9
|
import { isUnderspecifiedForExecution, applyRalplanGate } from '../keyword-detector.js';
|
|
10
10
|
import { KEYWORD_TRIGGER_DEFINITIONS } from '../keyword-registry.js';
|
|
@@ -43,15 +43,17 @@ describe('keyword detector swarm/team compatibility', () => {
|
|
|
43
43
|
const primary = detectPrimaryKeyword('/prompts:architect, analyze this issue');
|
|
44
44
|
assert.equal(primary, null);
|
|
45
45
|
});
|
|
46
|
-
it('maps analyze
|
|
47
|
-
const match = detectPrimaryKeyword('please analyze this workflow');
|
|
46
|
+
it('maps explicit $analyze invocation to analyze skill', () => {
|
|
47
|
+
const match = detectPrimaryKeyword('please run $analyze on this workflow');
|
|
48
48
|
assert.ok(match);
|
|
49
49
|
assert.equal(match.skill, 'analyze');
|
|
50
|
+
assert.equal(match.keyword.toLowerCase(), '$analyze');
|
|
50
51
|
});
|
|
51
52
|
it('maps code-review keyword variants to code-review skill', () => {
|
|
52
|
-
const hyphen = detectPrimaryKeyword('run code-review before merge');
|
|
53
|
+
const hyphen = detectPrimaryKeyword('run $code-review before merge');
|
|
53
54
|
assert.ok(hyphen);
|
|
54
55
|
assert.equal(hyphen.skill, 'code-review');
|
|
56
|
+
assert.equal(hyphen.keyword.toLowerCase(), '$code-review');
|
|
55
57
|
const spaced = detectPrimaryKeyword('please do a code review');
|
|
56
58
|
assert.ok(spaced);
|
|
57
59
|
assert.equal(spaced.skill, 'code-review');
|
|
@@ -79,8 +81,8 @@ describe('keyword detector swarm/team compatibility', () => {
|
|
|
79
81
|
assert.equal(match.skill, 'team');
|
|
80
82
|
assert.match(match.keyword.toLowerCase(), /swarm/);
|
|
81
83
|
});
|
|
82
|
-
it('keeps swarm trigger priority aligned with team
|
|
83
|
-
const teamMatch = detectKeywords('use team agents for this').find((entry) => entry.skill === 'team');
|
|
84
|
+
it('keeps swarm trigger priority aligned with explicit team invocation', () => {
|
|
85
|
+
const teamMatch = detectKeywords('use $team agents for this').find((entry) => entry.skill === 'team');
|
|
84
86
|
const swarmMatch = detectKeywords('use swarm for this').find((entry) => entry.skill === 'team');
|
|
85
87
|
assert.ok(teamMatch);
|
|
86
88
|
assert.ok(swarmMatch);
|
|
@@ -94,6 +96,10 @@ describe('keyword detector swarm/team compatibility', () => {
|
|
|
94
96
|
const match = detectPrimaryKeyword('the team reviewed the document and shared feedback');
|
|
95
97
|
assert.equal(match, null);
|
|
96
98
|
});
|
|
99
|
+
it('does not trigger team from bare skill-name phrasing without $ invocation', () => {
|
|
100
|
+
const match = detectPrimaryKeyword('please use team agents for this');
|
|
101
|
+
assert.equal(match, null);
|
|
102
|
+
});
|
|
97
103
|
it('still triggers team for explicit $team invocation', () => {
|
|
98
104
|
const match = detectPrimaryKeyword('please run $team now');
|
|
99
105
|
assert.ok(match);
|
|
@@ -113,8 +119,8 @@ describe('keyword detector swarm/team compatibility', () => {
|
|
|
113
119
|
assert.equal(match.skill, 'ralph');
|
|
114
120
|
assert.equal(match.keyword.toLowerCase(), '$ralph');
|
|
115
121
|
});
|
|
116
|
-
it('prefers ralplan over ralph when both
|
|
117
|
-
const match = detectPrimaryKeyword('
|
|
122
|
+
it('prefers ralplan over ralph follow-up language when both implicit routes are present', () => {
|
|
123
|
+
const match = detectPrimaryKeyword('keep going but do consensus plan first');
|
|
118
124
|
assert.ok(match);
|
|
119
125
|
assert.equal(match.skill, 'ralplan');
|
|
120
126
|
});
|
|
@@ -181,14 +187,44 @@ describe('keyword detector swarm/team compatibility', () => {
|
|
|
181
187
|
assert.equal(detectPrimaryKeyword('running 8 tests in parallel across 4 workers'), null);
|
|
182
188
|
});
|
|
183
189
|
});
|
|
190
|
+
describe('autoresearch keyword detection', () => {
|
|
191
|
+
it('detects explicit $autoresearch invocation', () => {
|
|
192
|
+
const match = detectPrimaryKeyword('please run $autoresearch now');
|
|
193
|
+
assert.ok(match);
|
|
194
|
+
assert.equal(match.skill, 'autoresearch');
|
|
195
|
+
assert.equal(match.keyword.toLowerCase(), '$autoresearch');
|
|
196
|
+
});
|
|
197
|
+
it('does not detect bare autoresearch phrasing without explicit $ invocation', () => {
|
|
198
|
+
const match = detectPrimaryKeyword('please use autoresearch workflow for this mission');
|
|
199
|
+
assert.equal(match, null);
|
|
200
|
+
});
|
|
201
|
+
it('does not trigger autoresearch from incidental prose', () => {
|
|
202
|
+
const match = detectPrimaryKeyword('Karpathy did autoresearch before native hooks existed');
|
|
203
|
+
assert.equal(match, null);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('explicit skill-name invocation requirement', () => {
|
|
207
|
+
it('does not trigger analyze from bare skill-name usage', () => {
|
|
208
|
+
assert.equal(detectPrimaryKeyword('please analyze this workflow'), null);
|
|
209
|
+
});
|
|
210
|
+
it('does not trigger autoresearch from bare skill-name usage', () => {
|
|
211
|
+
assert.equal(detectPrimaryKeyword('please run autoresearch now'), null);
|
|
212
|
+
});
|
|
213
|
+
it('does not trigger ralph from bare skill-name usage', () => {
|
|
214
|
+
assert.equal(detectPrimaryKeyword('please use ralph for this task'), null);
|
|
215
|
+
});
|
|
216
|
+
it('does not trigger ralplan from bare skill-name usage', () => {
|
|
217
|
+
assert.equal(detectPrimaryKeyword('please do ralplan first'), null);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
184
220
|
describe('keyword registry coverage', () => {
|
|
185
221
|
it('includes key team/swarm aliases in runtime keyword registry', () => {
|
|
186
222
|
const registryKeywords = new Set(KEYWORD_TRIGGER_DEFINITIONS.map((v) => v.keyword.toLowerCase()));
|
|
187
|
-
assert.ok(registryKeywords.has('ultraqa'));
|
|
188
|
-
assert.ok(registryKeywords.has('analyze'));
|
|
223
|
+
assert.ok(registryKeywords.has('$ultraqa'));
|
|
224
|
+
assert.ok(registryKeywords.has('$analyze'));
|
|
189
225
|
assert.ok(registryKeywords.has('investigate'));
|
|
190
226
|
assert.ok(registryKeywords.has('code review'));
|
|
191
|
-
assert.ok(registryKeywords.has('code-review'));
|
|
227
|
+
assert.ok(registryKeywords.has('$code-review'));
|
|
192
228
|
assert.ok(registryKeywords.has('coordinated team'));
|
|
193
229
|
assert.ok(registryKeywords.has('swarm'));
|
|
194
230
|
assert.ok(registryKeywords.has('coordinated swarm'));
|
|
@@ -198,6 +234,7 @@ describe('keyword registry coverage', () => {
|
|
|
198
234
|
assert.ok(registryKeywords.has('wiki query'));
|
|
199
235
|
assert.ok(registryKeywords.has('wiki add'));
|
|
200
236
|
assert.ok(registryKeywords.has('wiki lint'));
|
|
237
|
+
assert.ok(registryKeywords.has('$autoresearch'));
|
|
201
238
|
});
|
|
202
239
|
});
|
|
203
240
|
describe('keyword detector skill-active-state lifecycle', () => {
|
|
@@ -208,7 +245,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
208
245
|
await mkdir(stateDir, { recursive: true });
|
|
209
246
|
const result = await recordSkillActivation({
|
|
210
247
|
stateDir,
|
|
211
|
-
text: 'please run autopilot and keep going',
|
|
248
|
+
text: 'please run $autopilot and keep going',
|
|
212
249
|
sessionId: 'sess-1',
|
|
213
250
|
threadId: 'thread-1',
|
|
214
251
|
turnId: 'turn-1',
|
|
@@ -389,6 +426,53 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
389
426
|
await rm(cwd, { recursive: true, force: true });
|
|
390
427
|
}
|
|
391
428
|
});
|
|
429
|
+
it('seeds executing state for autoresearch prompt-submit activation', async () => {
|
|
430
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autoresearch-'));
|
|
431
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
432
|
+
try {
|
|
433
|
+
await mkdir(stateDir, { recursive: true });
|
|
434
|
+
const result = await recordSkillActivation({
|
|
435
|
+
stateDir,
|
|
436
|
+
text: '$autoresearch continue the mission',
|
|
437
|
+
sessionId: 'sess-autoresearch',
|
|
438
|
+
nowIso: '2026-04-17T00:00:00.000Z',
|
|
439
|
+
});
|
|
440
|
+
assert.ok(result);
|
|
441
|
+
assert.equal(result.skill, 'autoresearch');
|
|
442
|
+
assert.equal(result.phase, 'executing');
|
|
443
|
+
assert.equal(result.initialized_mode, 'autoresearch');
|
|
444
|
+
assert.equal(result.initialized_state_path, '.omx/state/sessions/sess-autoresearch/autoresearch-state.json');
|
|
445
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autoresearch', 'autoresearch-state.json'), 'utf-8'));
|
|
446
|
+
assert.equal(modeState.mode, 'autoresearch');
|
|
447
|
+
assert.equal(modeState.active, true);
|
|
448
|
+
assert.equal(modeState.current_phase, 'executing');
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
await rm(cwd, { recursive: true, force: true });
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
it('preserves the planning skill when ralplan and autoresearch are invoked together', async () => {
|
|
455
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autoresearch-planning-precedence-'));
|
|
456
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
457
|
+
try {
|
|
458
|
+
await mkdir(stateDir, { recursive: true });
|
|
459
|
+
const result = await recordSkillActivation({
|
|
460
|
+
stateDir,
|
|
461
|
+
text: '$ralplan $autoresearch wire the mission loop',
|
|
462
|
+
sessionId: 'sess-autoresearch-precedence',
|
|
463
|
+
nowIso: '2026-04-17T00:05:00.000Z',
|
|
464
|
+
});
|
|
465
|
+
assert.equal(result?.transition_error, undefined);
|
|
466
|
+
assert.equal(result?.skill, 'ralplan');
|
|
467
|
+
assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralplan']);
|
|
468
|
+
assert.deepEqual(result?.deferred_skills, ['autoresearch']);
|
|
469
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autoresearch-precedence', 'ralplan-state.json')), true);
|
|
470
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autoresearch-precedence', 'autoresearch-state.json')), false);
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
await rm(cwd, { recursive: true, force: true });
|
|
474
|
+
}
|
|
475
|
+
});
|
|
392
476
|
it('seeds first-class state for ralplan prompt-submit activation', async () => {
|
|
393
477
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ralplan-'));
|
|
394
478
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -426,7 +510,17 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
426
510
|
session_id: 'sess-handoff',
|
|
427
511
|
active_skills: [{ skill: 'deep-interview', phase: 'planning', active: true, session_id: 'sess-handoff' }],
|
|
428
512
|
}, null, 2));
|
|
429
|
-
await writeFile(join(stateDir, 'sessions', 'sess-handoff', 'deep-interview-state.json'), JSON.stringify({
|
|
513
|
+
await writeFile(join(stateDir, 'sessions', 'sess-handoff', 'deep-interview-state.json'), JSON.stringify({
|
|
514
|
+
active: true,
|
|
515
|
+
mode: 'deep-interview',
|
|
516
|
+
current_phase: 'intent-first',
|
|
517
|
+
question_enforcement: {
|
|
518
|
+
obligation_id: 'obligation-handoff',
|
|
519
|
+
source: 'omx-question',
|
|
520
|
+
status: 'pending',
|
|
521
|
+
requested_at: '2026-04-09T23:59:00.000Z',
|
|
522
|
+
},
|
|
523
|
+
}, null, 2));
|
|
430
524
|
const result = await recordSkillActivation({
|
|
431
525
|
stateDir,
|
|
432
526
|
text: '$ralplan implement the approved contract',
|
|
@@ -438,6 +532,9 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
438
532
|
const completed = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-handoff', 'deep-interview-state.json'), 'utf-8'));
|
|
439
533
|
assert.equal(completed.active, false);
|
|
440
534
|
assert.equal(completed.current_phase, 'completed');
|
|
535
|
+
assert.equal(completed.question_enforcement?.status, 'cleared');
|
|
536
|
+
assert.equal(completed.question_enforcement?.clear_reason, 'handoff');
|
|
537
|
+
assert.ok(completed.question_enforcement?.cleared_at);
|
|
441
538
|
}
|
|
442
539
|
finally {
|
|
443
540
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -647,6 +744,84 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
647
744
|
await rm(cwd, { recursive: true, force: true });
|
|
648
745
|
}
|
|
649
746
|
});
|
|
747
|
+
it('creates the session-scoped deep-interview state directory before persisting mode state', async () => {
|
|
748
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-session-dir-'));
|
|
749
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
750
|
+
try {
|
|
751
|
+
await mkdir(stateDir, { recursive: true });
|
|
752
|
+
await persistDeepInterviewModeState(stateDir, {
|
|
753
|
+
version: 1,
|
|
754
|
+
active: true,
|
|
755
|
+
skill: 'deep-interview',
|
|
756
|
+
keyword: 'deep interview',
|
|
757
|
+
phase: 'planning',
|
|
758
|
+
activated_at: '2026-02-25T00:00:00.000Z',
|
|
759
|
+
updated_at: '2026-02-25T00:00:00.000Z',
|
|
760
|
+
source: 'keyword-detector',
|
|
761
|
+
session_id: 'sess-sync',
|
|
762
|
+
input_lock: {
|
|
763
|
+
active: true,
|
|
764
|
+
scope: 'deep-interview-auto-approval',
|
|
765
|
+
acquired_at: '2026-02-25T00:00:00.000Z',
|
|
766
|
+
blocked_inputs: [...DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS],
|
|
767
|
+
message: DEEP_INTERVIEW_INPUT_LOCK_MESSAGE,
|
|
768
|
+
},
|
|
769
|
+
}, '2026-02-25T00:00:00.000Z', null, { sessionId: 'sess-sync' });
|
|
770
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-sync', DEEP_INTERVIEW_STATE_FILE), 'utf-8'));
|
|
771
|
+
assert.equal(modeState.active, true);
|
|
772
|
+
assert.equal(modeState.mode, 'deep-interview');
|
|
773
|
+
}
|
|
774
|
+
finally {
|
|
775
|
+
await rm(cwd, { recursive: true, force: true });
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
it('clears stale pending deep-interview question enforcement when deep-interview is reactivated', async () => {
|
|
779
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-reactivation-'));
|
|
780
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
781
|
+
try {
|
|
782
|
+
await mkdir(join(stateDir, 'sessions', 'sess-reactivate'), { recursive: true });
|
|
783
|
+
await writeFile(join(stateDir, 'sessions', 'sess-reactivate', DEEP_INTERVIEW_STATE_FILE), JSON.stringify({
|
|
784
|
+
active: false,
|
|
785
|
+
mode: 'deep-interview',
|
|
786
|
+
current_phase: 'completed',
|
|
787
|
+
started_at: '2026-04-10T00:00:00.000Z',
|
|
788
|
+
updated_at: '2026-04-10T00:10:00.000Z',
|
|
789
|
+
completed_at: '2026-04-10T00:10:00.000Z',
|
|
790
|
+
question_enforcement: {
|
|
791
|
+
obligation_id: 'obligation-reactivate',
|
|
792
|
+
source: 'omx-question',
|
|
793
|
+
status: 'pending',
|
|
794
|
+
requested_at: '2026-04-10T00:05:00.000Z',
|
|
795
|
+
},
|
|
796
|
+
}, null, 2));
|
|
797
|
+
await persistDeepInterviewModeState(stateDir, {
|
|
798
|
+
version: 1,
|
|
799
|
+
active: true,
|
|
800
|
+
skill: 'deep-interview',
|
|
801
|
+
keyword: 'deep interview',
|
|
802
|
+
phase: 'planning',
|
|
803
|
+
activated_at: '2026-04-10T00:11:00.000Z',
|
|
804
|
+
updated_at: '2026-04-10T00:11:00.000Z',
|
|
805
|
+
source: 'keyword-detector',
|
|
806
|
+
session_id: 'sess-reactivate',
|
|
807
|
+
input_lock: {
|
|
808
|
+
active: true,
|
|
809
|
+
scope: 'deep-interview-auto-approval',
|
|
810
|
+
acquired_at: '2026-04-10T00:11:00.000Z',
|
|
811
|
+
blocked_inputs: [...DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS],
|
|
812
|
+
message: DEEP_INTERVIEW_INPUT_LOCK_MESSAGE,
|
|
813
|
+
},
|
|
814
|
+
}, '2026-04-10T00:11:00.000Z', null, { sessionId: 'sess-reactivate' });
|
|
815
|
+
const reactivated = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-reactivate', DEEP_INTERVIEW_STATE_FILE), 'utf-8'));
|
|
816
|
+
assert.equal(reactivated.active, true);
|
|
817
|
+
assert.equal(reactivated.question_enforcement?.status, 'cleared');
|
|
818
|
+
assert.equal(reactivated.question_enforcement?.clear_reason, 'handoff');
|
|
819
|
+
assert.ok(reactivated.question_enforcement?.cleared_at);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
await rm(cwd, { recursive: true, force: true });
|
|
823
|
+
}
|
|
824
|
+
});
|
|
650
825
|
it('releases the deep-interview input lock on abort via cancel keyword', async () => {
|
|
651
826
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-deep-interview-abort-'));
|
|
652
827
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -654,7 +829,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
654
829
|
await mkdir(stateDir, { recursive: true });
|
|
655
830
|
await recordSkillActivation({
|
|
656
831
|
stateDir,
|
|
657
|
-
text: 'please run deep
|
|
832
|
+
text: 'please run $deep-interview',
|
|
658
833
|
nowIso: '2026-02-25T00:00:00.000Z',
|
|
659
834
|
});
|
|
660
835
|
const result = await recordSkillActivation({
|
|
@@ -719,7 +894,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
719
894
|
});
|
|
720
895
|
const result = await recordSkillActivation({
|
|
721
896
|
stateDir: join('/definitely-missing', 'nested', 'state-dir'),
|
|
722
|
-
text: 'please run autopilot',
|
|
897
|
+
text: 'please run $autopilot',
|
|
723
898
|
nowIso: '2026-02-25T00:00:00.000Z',
|
|
724
899
|
});
|
|
725
900
|
assert.ok(result);
|
|
@@ -737,7 +912,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
737
912
|
version: 1,
|
|
738
913
|
active: true,
|
|
739
914
|
skill: 'autopilot',
|
|
740
|
-
keyword: 'autopilot',
|
|
915
|
+
keyword: '$autopilot',
|
|
741
916
|
phase: 'planning',
|
|
742
917
|
activated_at: '2026-02-25T00:00:00.000Z',
|
|
743
918
|
updated_at: '2026-02-25T00:10:00.000Z',
|
|
@@ -749,6 +924,8 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
749
924
|
nowIso: '2026-02-26T00:00:00.000Z',
|
|
750
925
|
});
|
|
751
926
|
assert.ok(result);
|
|
927
|
+
assert.equal(result.skill, 'autopilot');
|
|
928
|
+
assert.equal(result.transition_error, undefined);
|
|
752
929
|
assert.equal(result.activated_at, '2026-02-25T00:00:00.000Z');
|
|
753
930
|
assert.equal(result.updated_at, '2026-02-26T00:00:00.000Z');
|
|
754
931
|
}
|
|
@@ -790,6 +967,9 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
790
967
|
nowIso: '2026-02-26T00:00:00.000Z',
|
|
791
968
|
});
|
|
792
969
|
assert.ok(result);
|
|
970
|
+
assert.equal(result.skill, 'autopilot');
|
|
971
|
+
assert.equal(result.phase, 'planning');
|
|
972
|
+
assert.equal(result.transition_error, undefined);
|
|
793
973
|
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot', 'autopilot-state.json'), 'utf-8'));
|
|
794
974
|
assert.equal(modeState.current_phase, 'execution');
|
|
795
975
|
assert.equal(modeState.started_at, '2026-02-25T00:00:00.000Z');
|
|
@@ -852,6 +1032,8 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
852
1032
|
nowIso: '2026-02-26T00:00:00.000Z',
|
|
853
1033
|
});
|
|
854
1034
|
assert.ok(result);
|
|
1035
|
+
assert.equal(result.skill, 'ralph');
|
|
1036
|
+
assert.equal(result.transition_error, undefined);
|
|
855
1037
|
const modeState = JSON.parse(await readFile(join(stateDir, 'ralph-state.json'), 'utf-8'));
|
|
856
1038
|
assert.equal(modeState.current_phase, 'verifying');
|
|
857
1039
|
assert.equal(modeState.iteration, 3);
|
|
@@ -861,6 +1043,115 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
861
1043
|
await rm(cwd, { recursive: true, force: true });
|
|
862
1044
|
}
|
|
863
1045
|
});
|
|
1046
|
+
it('routes bare keep-going continuation to the active autopilot skill instead of generic ralph continuation', async () => {
|
|
1047
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-autopilot-bare-continuation-'));
|
|
1048
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1049
|
+
try {
|
|
1050
|
+
await mkdir(join(stateDir, 'sessions', 'sess-autopilot-bare'), { recursive: true });
|
|
1051
|
+
await writeFile(join(stateDir, 'sessions', 'sess-autopilot-bare', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1052
|
+
version: 1,
|
|
1053
|
+
active: true,
|
|
1054
|
+
skill: 'autopilot',
|
|
1055
|
+
keyword: '$autopilot',
|
|
1056
|
+
phase: 'planning',
|
|
1057
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
1058
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1059
|
+
source: 'keyword-detector',
|
|
1060
|
+
session_id: 'sess-autopilot-bare',
|
|
1061
|
+
active_skills: [
|
|
1062
|
+
{
|
|
1063
|
+
skill: 'autopilot',
|
|
1064
|
+
phase: 'planning',
|
|
1065
|
+
active: true,
|
|
1066
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
1067
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1068
|
+
session_id: 'sess-autopilot-bare',
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
}, null, 2));
|
|
1072
|
+
await writeFile(join(stateDir, 'sessions', 'sess-autopilot-bare', 'autopilot-state.json'), JSON.stringify({
|
|
1073
|
+
active: true,
|
|
1074
|
+
mode: 'autopilot',
|
|
1075
|
+
current_phase: 'execution',
|
|
1076
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
1077
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1078
|
+
session_id: 'sess-autopilot-bare',
|
|
1079
|
+
state: { context_snapshot_path: '.omx/context/autopilot.md' },
|
|
1080
|
+
}, null, 2));
|
|
1081
|
+
const result = await recordSkillActivation({
|
|
1082
|
+
stateDir,
|
|
1083
|
+
text: '\\ keep going now',
|
|
1084
|
+
sessionId: 'sess-autopilot-bare',
|
|
1085
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
1086
|
+
});
|
|
1087
|
+
assert.ok(result);
|
|
1088
|
+
assert.equal(result.skill, 'autopilot');
|
|
1089
|
+
assert.equal(result.keyword, '$autopilot');
|
|
1090
|
+
assert.equal(result.transition_error, undefined);
|
|
1091
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-bare', 'autopilot-state.json'), 'utf-8'));
|
|
1092
|
+
assert.equal(modeState.current_phase, 'execution');
|
|
1093
|
+
assert.equal(modeState.state?.context_snapshot_path, '.omx/context/autopilot.md');
|
|
1094
|
+
assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-bare', 'ralph-state.json')), false);
|
|
1095
|
+
}
|
|
1096
|
+
finally {
|
|
1097
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
it('routes bare keep-going continuation to the active ralph skill instead of resetting through generic keep-going detection', async () => {
|
|
1101
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-ralph-bare-continuation-'));
|
|
1102
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
1103
|
+
try {
|
|
1104
|
+
await mkdir(join(stateDir, 'sessions', 'sess-ralph-bare'), { recursive: true });
|
|
1105
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralph-bare', SKILL_ACTIVE_STATE_FILE), JSON.stringify({
|
|
1106
|
+
version: 1,
|
|
1107
|
+
active: true,
|
|
1108
|
+
skill: 'ralph',
|
|
1109
|
+
keyword: '$ralph',
|
|
1110
|
+
phase: 'executing',
|
|
1111
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
1112
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1113
|
+
source: 'keyword-detector',
|
|
1114
|
+
session_id: 'sess-ralph-bare',
|
|
1115
|
+
active_skills: [
|
|
1116
|
+
{
|
|
1117
|
+
skill: 'ralph',
|
|
1118
|
+
phase: 'executing',
|
|
1119
|
+
active: true,
|
|
1120
|
+
activated_at: '2026-04-19T00:00:00.000Z',
|
|
1121
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1122
|
+
session_id: 'sess-ralph-bare',
|
|
1123
|
+
},
|
|
1124
|
+
],
|
|
1125
|
+
}, null, 2));
|
|
1126
|
+
await writeFile(join(stateDir, 'sessions', 'sess-ralph-bare', 'ralph-state.json'), JSON.stringify({
|
|
1127
|
+
active: true,
|
|
1128
|
+
mode: 'ralph',
|
|
1129
|
+
current_phase: 'verifying',
|
|
1130
|
+
started_at: '2026-04-19T00:00:00.000Z',
|
|
1131
|
+
updated_at: '2026-04-19T00:10:00.000Z',
|
|
1132
|
+
iteration: 7,
|
|
1133
|
+
max_iterations: 50,
|
|
1134
|
+
session_id: 'sess-ralph-bare',
|
|
1135
|
+
}, null, 2));
|
|
1136
|
+
const result = await recordSkillActivation({
|
|
1137
|
+
stateDir,
|
|
1138
|
+
text: 'keep going now',
|
|
1139
|
+
sessionId: 'sess-ralph-bare',
|
|
1140
|
+
nowIso: '2026-04-19T00:15:00.000Z',
|
|
1141
|
+
});
|
|
1142
|
+
assert.ok(result);
|
|
1143
|
+
assert.equal(result.skill, 'ralph');
|
|
1144
|
+
assert.equal(result.keyword, '$ralph');
|
|
1145
|
+
assert.equal(result.transition_error, undefined);
|
|
1146
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-ralph-bare', 'ralph-state.json'), 'utf-8'));
|
|
1147
|
+
assert.equal(modeState.current_phase, 'verifying');
|
|
1148
|
+
assert.equal(modeState.iteration, 7);
|
|
1149
|
+
assert.equal(modeState.max_iterations, 50);
|
|
1150
|
+
}
|
|
1151
|
+
finally {
|
|
1152
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1153
|
+
}
|
|
1154
|
+
});
|
|
864
1155
|
it('denies switching away from a standalone workflow without explicit clear', async () => {
|
|
865
1156
|
const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-skill-switch-deny-'));
|
|
866
1157
|
const stateDir = join(cwd, '.omx', 'state');
|
|
@@ -879,7 +1170,7 @@ describe('keyword detector skill-active-state lifecycle', () => {
|
|
|
879
1170
|
}));
|
|
880
1171
|
const result = await recordSkillActivation({
|
|
881
1172
|
stateDir,
|
|
882
|
-
text: 'please run ralph now',
|
|
1173
|
+
text: 'please run $ralph now',
|
|
883
1174
|
nowIso: '2026-02-26T00:00:00.000Z',
|
|
884
1175
|
});
|
|
885
1176
|
assert.ok(result);
|