oh-my-opencode 5.0.0-beta.8 → 5.0.0-beta.9
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/.agents/command/publish.md +44 -16
- package/.agents/skills/publish/SKILL.md +44 -16
- package/.opencode/command/publish.md +44 -16
- package/dist/agents/atlas/agent.d.ts +0 -1
- package/dist/cli/index.js +34 -31
- package/dist/cli/run/on-complete-hook.d.ts +2 -0
- package/dist/cli-node/index.js +34 -31
- package/dist/hooks/atlas/final-wave-approval-gate.test-support.d.ts +50 -0
- package/dist/hooks/atlas/system-reminder-templates.d.ts +0 -1
- package/dist/index.js +25 -24
- package/dist/shared/shell-env.d.ts +1 -1
- package/dist/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/dist/tui.js +2 -2
- package/package.json +15 -14
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +2 -2
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +2 -2
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +3 -17
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
- package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +16 -23
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
- package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +37 -69
- package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +1 -15
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +0 -3
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +2 -16
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +3 -9
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
- package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +2 -35
- package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +4 -5
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +20 -20
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +4 -24
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
- package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
- package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
- package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +101 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +1 -119
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
- package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
- package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
- package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +0 -3
- package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +7 -2
- package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
- package/dist/tools/call-omo-agent/background-agent-executor.d.ts +0 -5
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
- package/packages/omo-codex/plugin/test/ulw-plan-scope-contract.test.mjs +0 -24
|
@@ -77,6 +77,10 @@ const DYNAMIC_BUNDLED_ONLY_ENV = {
|
|
|
77
77
|
PLUGIN_ROOT: COMPONENT_ROOT,
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
// Runtime sentinel: the rules hook emits and re-greps this marker when
|
|
81
|
+
// deduplicating injected rules against the transcript (transcript-rule-filter.ts).
|
|
82
|
+
const INSTRUCTIONS_FROM = "Instructions from: ";
|
|
83
|
+
|
|
80
84
|
afterEach(() => {
|
|
81
85
|
for (const directory of tempDirectories.splice(0)) {
|
|
82
86
|
rmSync(directory, { recursive: true, force: true });
|
|
@@ -88,20 +92,13 @@ function makeTempProject(): { root: string; pluginData: string } {
|
|
|
88
92
|
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-rules-data-"));
|
|
89
93
|
tempDirectories.push(root, pluginData);
|
|
90
94
|
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
|
|
91
|
-
writeFileSync(path.join(root, "AGENTS.md"), "
|
|
92
|
-
writeFileSync(path.join(root, "CLAUDE.md"), "
|
|
93
|
-
writeFileSync(path.join(root, "CONTEXT.md"), "
|
|
95
|
+
writeFileSync(path.join(root, "AGENTS.md"), "AGENTS_MD_SENTINEL");
|
|
96
|
+
writeFileSync(path.join(root, "CLAUDE.md"), "CLAUDE_MD_SENTINEL");
|
|
97
|
+
writeFileSync(path.join(root, "CONTEXT.md"), "CTX_RULE_SENTINEL");
|
|
94
98
|
mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
|
|
95
99
|
writeFileSync(
|
|
96
100
|
path.join(root, ".omo", "rules", "typescript.md"),
|
|
97
|
-
[
|
|
98
|
-
"---",
|
|
99
|
-
"description: TypeScript",
|
|
100
|
-
'globs: ["**/*.ts", "**/*.tsx"]',
|
|
101
|
-
"---",
|
|
102
|
-
"",
|
|
103
|
-
"Prefer strict TypeScript for all source files.",
|
|
104
|
-
].join("\n"),
|
|
101
|
+
["---", "description: TypeScript", 'globs: ["**/*.ts", "**/*.tsx"]', "---", "", "TS_RULE_SENTINEL"].join("\n"),
|
|
105
102
|
);
|
|
106
103
|
mkdirSync(path.join(root, "src"), { recursive: true });
|
|
107
104
|
writeFileSync(path.join(root, "src", "app.ts"), "export const app = true;\n");
|
|
@@ -224,11 +221,10 @@ describe("codex rules hooks", () => {
|
|
|
224
221
|
// then
|
|
225
222
|
const parsed = parseHookOutput(output);
|
|
226
223
|
expect(parsed.hookSpecificOutput?.hookEventName).toBe("SessionStart");
|
|
227
|
-
expect(parsed.hookSpecificOutput?.additionalContext).toContain("## Project Instructions");
|
|
228
224
|
expect(parsed.hookSpecificOutput?.additionalContext).toContain(
|
|
229
|
-
|
|
225
|
+
`${INSTRUCTIONS_FROM}${path.join(root, "CONTEXT.md")}`,
|
|
230
226
|
);
|
|
231
|
-
expect(parsed.hookSpecificOutput?.additionalContext).toContain("
|
|
227
|
+
expect(parsed.hookSpecificOutput?.additionalContext).toContain("CTX_RULE_SENTINEL");
|
|
232
228
|
});
|
|
233
229
|
|
|
234
230
|
it("#given default auto sources #when SessionStart runs #then native Codex AGENTS.md is not duplicated", async () => {
|
|
@@ -242,11 +238,11 @@ describe("codex rules hooks", () => {
|
|
|
242
238
|
|
|
243
239
|
// then
|
|
244
240
|
const parsed = parseHookOutput(output);
|
|
245
|
-
expect(parsed.hookSpecificOutput?.additionalContext).toContain(
|
|
246
|
-
|
|
247
|
-
expect(parsed.hookSpecificOutput?.additionalContext).not.toContain(
|
|
248
|
-
"Project CLAUDE.md should stay outside rules hook context.",
|
|
241
|
+
expect(parsed.hookSpecificOutput?.additionalContext).toContain(
|
|
242
|
+
`${INSTRUCTIONS_FROM}${path.join(root, "CONTEXT.md")}`,
|
|
249
243
|
);
|
|
244
|
+
expect(parsed.hookSpecificOutput?.additionalContext).not.toContain("AGENTS_MD_SENTINEL");
|
|
245
|
+
expect(parsed.hookSpecificOutput?.additionalContext).not.toContain("CLAUDE_MD_SENTINEL");
|
|
250
246
|
});
|
|
251
247
|
|
|
252
248
|
it("#given project AGENTS.md #when SessionStart runs #then rules hook leaves AGENTS.md to Codex native handling", async () => {
|
|
@@ -320,9 +316,8 @@ describe("codex rules hooks", () => {
|
|
|
320
316
|
// then
|
|
321
317
|
expect(resumeOutput).toBe("");
|
|
322
318
|
const clearContext = parseHookOutput(clearOutput).hookSpecificOutput?.additionalContext ?? "";
|
|
323
|
-
expect(clearContext).toContain(
|
|
324
|
-
expect(clearContext).toContain(
|
|
325
|
-
expect(clearContext).toContain("Always wear safety goggles");
|
|
319
|
+
expect(clearContext).toContain(`${INSTRUCTIONS_FROM}${path.join(root, "CONTEXT.md")}`);
|
|
320
|
+
expect(clearContext).toContain("CTX_RULE_SENTINEL");
|
|
326
321
|
});
|
|
327
322
|
|
|
328
323
|
it("#given static context remains in transcript but cache is missing #when SessionStart runs #then it emits no duplicate context", async () => {
|
|
@@ -363,10 +358,8 @@ describe("codex rules hooks", () => {
|
|
|
363
358
|
// regression line: prior versions emitted "src\\app.ts" on Windows.
|
|
364
359
|
const parsed = parseHookOutput(output);
|
|
365
360
|
expect(parsed.hookSpecificOutput?.hookEventName).toBe("PostToolUse");
|
|
366
|
-
expect(parsed.hookSpecificOutput?.additionalContext).toContain(
|
|
367
|
-
|
|
368
|
-
);
|
|
369
|
-
expect(parsed.hookSpecificOutput?.additionalContext).toContain("Prefer strict TypeScript");
|
|
361
|
+
expect(parsed.hookSpecificOutput?.additionalContext).toContain("src/app.ts");
|
|
362
|
+
expect(parsed.hookSpecificOutput?.additionalContext).toContain("TS_RULE_SENTINEL");
|
|
370
363
|
expect(parsed.hookSpecificOutput?.additionalContext ?? "").not.toContain("src\\app.ts");
|
|
371
364
|
expect(output).not.toContain("updatedMCPToolOutput");
|
|
372
365
|
expect(output).not.toContain("suppressOutput");
|
|
@@ -396,9 +389,9 @@ describe("codex rules hooks", () => {
|
|
|
396
389
|
const parsed = parseHookOutput(output);
|
|
397
390
|
const additionalContext = parsed.hookSpecificOutput?.additionalContext ?? "";
|
|
398
391
|
expect(parsed.hookSpecificOutput?.hookEventName).toBe("PostToolUse");
|
|
399
|
-
expect(additionalContext).toContain("
|
|
392
|
+
expect(additionalContext).toContain("src/app.ts");
|
|
400
393
|
expect(additionalContext).not.toContain("src\\app.ts");
|
|
401
|
-
expect(occurrenceCount(additionalContext, "
|
|
394
|
+
expect(occurrenceCount(additionalContext, "TS_RULE_SENTINEL")).toBe(1);
|
|
402
395
|
});
|
|
403
396
|
|
|
404
397
|
it("#given dynamic context already injected #when PostToolUse repeats #then emits no duplicate context", async () => {
|
|
@@ -437,10 +430,10 @@ describe("codex rules hooks", () => {
|
|
|
437
430
|
// then
|
|
438
431
|
const firstContext = parseHookOutput(firstOutput).hookSpecificOutput?.additionalContext ?? "";
|
|
439
432
|
const secondContext = parseHookOutput(secondOutput).hookSpecificOutput?.additionalContext ?? "";
|
|
440
|
-
expect(firstContext).toContain("
|
|
441
|
-
expect(firstContext).not.toContain("
|
|
442
|
-
expect(secondContext).toContain("
|
|
443
|
-
expect(secondContext).not.toContain("
|
|
433
|
+
expect(firstContext).toContain("bundled-rules/hephaestus/gpt-5.5.md");
|
|
434
|
+
expect(firstContext).not.toContain("bundled-rules/hephaestus/gpt-5.6.md");
|
|
435
|
+
expect(secondContext).toContain("bundled-rules/hephaestus/gpt-5.6.md");
|
|
436
|
+
expect(secondContext).not.toContain("bundled-rules/hephaestus/gpt-5.5.md");
|
|
444
437
|
});
|
|
445
438
|
|
|
446
439
|
it("#given default auto sources #when excluded AGENTS.md changes #then PostToolUse fingerprint stays stable", async () => {
|
|
@@ -496,7 +489,7 @@ describe("codex rules hooks", () => {
|
|
|
496
489
|
);
|
|
497
490
|
|
|
498
491
|
// then
|
|
499
|
-
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain("
|
|
492
|
+
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain("TS_RULE_SENTINEL");
|
|
500
493
|
});
|
|
501
494
|
|
|
502
495
|
it("#given cached dynamic target #when rule frontmatter changes #then PostToolUse rechecks the target", async () => {
|
|
@@ -505,15 +498,13 @@ describe("codex rules hooks", () => {
|
|
|
505
498
|
const filePath = path.join(root, "src", "app.ts");
|
|
506
499
|
const input = postToolUseInput(root, filePath);
|
|
507
500
|
await runPostToolUseHook(input, { pluginDataRoot: pluginData, env: RULES_ONLY_ENV });
|
|
508
|
-
writeTypeScriptRule(root, '"**/*.ts"', "
|
|
501
|
+
writeTypeScriptRule(root, '"**/*.ts"', "TS_RULE_V2_SENTINEL");
|
|
509
502
|
|
|
510
503
|
// when
|
|
511
504
|
const output = await runPostToolUseHook(input, { pluginDataRoot: pluginData, env: RULES_ONLY_ENV });
|
|
512
505
|
|
|
513
506
|
// then
|
|
514
|
-
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain(
|
|
515
|
-
"Prefer readonly TypeScript after rule edits.",
|
|
516
|
-
);
|
|
507
|
+
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain("TS_RULE_V2_SENTINEL");
|
|
517
508
|
});
|
|
518
509
|
|
|
519
510
|
it("#given cached dynamic context #when PostCompact runs #then PostToolUse emits no duplicate dynamic context", async () => {
|
|
@@ -633,7 +624,7 @@ describe("codex rules hooks", () => {
|
|
|
633
624
|
});
|
|
634
625
|
|
|
635
626
|
// then
|
|
636
|
-
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain("
|
|
627
|
+
expect(parseHookOutput(output).hookSpecificOutput?.additionalContext).toContain("TS_RULE_SENTINEL");
|
|
637
628
|
});
|
|
638
629
|
|
|
639
630
|
it("#given static-only mode #when PostToolUse runs #then emits no dynamic context", async () => {
|
|
@@ -8,13 +8,18 @@ const FORMAT_OPTIONS = {
|
|
|
8
8
|
maxResultChars: 10_000,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// Runtime sentinel: the rules hook emits and re-greps this marker when
|
|
12
|
+
// deduplicating injected rules against the transcript (transcript-rule-filter.ts).
|
|
13
|
+
const INSTRUCTIONS_FROM = "Instructions from: ";
|
|
14
|
+
|
|
11
15
|
describe("rules formatter hook context", () => {
|
|
12
16
|
it("#given multiline dynamic rules #when formatting PostToolUse context #then labels and bodies render on separate lines", () => {
|
|
13
17
|
// given
|
|
18
|
+
const body = "DYN_BODY_SENTINEL_LINE_1\n\nDYN_BODY_SENTINEL_LINE_2";
|
|
14
19
|
const rule = loadedRule({
|
|
15
20
|
path: "/repo/packages/CONTEXT.md",
|
|
16
21
|
relativePath: "packages/CONTEXT.md",
|
|
17
|
-
body
|
|
22
|
+
body,
|
|
18
23
|
});
|
|
19
24
|
|
|
20
25
|
// when
|
|
@@ -25,124 +30,86 @@ describe("rules formatter hook context", () => {
|
|
|
25
30
|
);
|
|
26
31
|
|
|
27
32
|
// then
|
|
28
|
-
expect(block).
|
|
29
|
-
|
|
30
|
-
"Additional project instructions matched for packages/omo-codex/plugin/components/ulw-loop/src/paths.ts:",
|
|
31
|
-
"",
|
|
32
|
-
"Instructions from: /repo/packages/CONTEXT.md",
|
|
33
|
-
"",
|
|
34
|
-
"# packages",
|
|
35
|
-
"",
|
|
36
|
-
"## OVERVIEW",
|
|
37
|
-
"23 sibling packages.",
|
|
38
|
-
"",
|
|
39
|
-
"## CONVENTIONS",
|
|
40
|
-
"Use npm.",
|
|
41
|
-
].join("\n"),
|
|
42
|
-
);
|
|
33
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/packages/CONTEXT.md\n\n${body}`);
|
|
34
|
+
expect(occurrenceCount(block, INSTRUCTIONS_FROM)).toBe(1);
|
|
43
35
|
});
|
|
44
36
|
|
|
45
37
|
it("#given static rules #when formatting SessionStart context #then it injects rule bodies inline", () => {
|
|
46
38
|
// given
|
|
39
|
+
const body = "STATIC_BODY_SENTINEL";
|
|
47
40
|
const rule = loadedRule({
|
|
48
41
|
path: "/repo/CONTEXT.md",
|
|
49
42
|
relativePath: "CONTEXT.md",
|
|
50
|
-
body
|
|
43
|
+
body,
|
|
51
44
|
});
|
|
52
45
|
|
|
53
46
|
// when
|
|
54
47
|
const block = formatStaticBlock([rule], FORMAT_OPTIONS);
|
|
55
48
|
|
|
56
49
|
// then
|
|
57
|
-
expect(block).
|
|
58
|
-
[
|
|
59
|
-
"## Project Instructions",
|
|
60
|
-
"",
|
|
61
|
-
"Instructions from: /repo/CONTEXT.md",
|
|
62
|
-
"",
|
|
63
|
-
"Keep generated hook context readable.",
|
|
64
|
-
].join("\n"),
|
|
65
|
-
);
|
|
50
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/CONTEXT.md\n\n${body}`);
|
|
66
51
|
});
|
|
67
52
|
|
|
68
53
|
it("#given CRLF and bare CR rule bodies #when formatting context #then it normalizes line endings", () => {
|
|
69
54
|
// given
|
|
70
55
|
const rule = loadedRule({
|
|
71
|
-
body: "
|
|
56
|
+
body: "A1\r\n B2\rC3",
|
|
72
57
|
});
|
|
73
58
|
|
|
74
59
|
// when
|
|
75
60
|
const block = formatDynamicBlock([rule], "src/app.ts", FORMAT_OPTIONS);
|
|
76
61
|
|
|
77
62
|
// then
|
|
78
|
-
expect(block).toContain("
|
|
63
|
+
expect(block).toContain("A1\n B2\nC3");
|
|
79
64
|
expect(block).not.toContain("\r");
|
|
80
65
|
});
|
|
81
66
|
|
|
82
67
|
it("#given duplicate static rules with different line endings #when formatting context #then it injects one copy", () => {
|
|
83
68
|
// given
|
|
69
|
+
const sharedBody = "SHARED_L1\r\nSHARED_L2";
|
|
70
|
+
const normalizedSharedBody = "SHARED_L1\nSHARED_L2";
|
|
84
71
|
const lfRule = loadedRule({
|
|
85
72
|
path: "/repo/CONTEXT.md",
|
|
86
73
|
relativePath: "CONTEXT.md",
|
|
87
|
-
body:
|
|
74
|
+
body: normalizedSharedBody,
|
|
88
75
|
});
|
|
89
76
|
const crlfRule = loadedRule({
|
|
90
77
|
path: "/repo/packages/CONTEXT.md",
|
|
91
78
|
relativePath: "packages/CONTEXT.md",
|
|
92
|
-
body:
|
|
79
|
+
body: sharedBody,
|
|
93
80
|
});
|
|
94
81
|
|
|
95
82
|
// when
|
|
96
83
|
const block = formatStaticBlock([lfRule, crlfRule], FORMAT_OPTIONS);
|
|
97
84
|
|
|
98
85
|
// then
|
|
99
|
-
expect(occurrenceCount(block,
|
|
100
|
-
expect(occurrenceCount(block,
|
|
86
|
+
expect(occurrenceCount(block, `${INSTRUCTIONS_FROM}/repo/CONTEXT.md`)).toBe(1);
|
|
87
|
+
expect(occurrenceCount(block, normalizedSharedBody)).toBe(1);
|
|
101
88
|
expect(block).not.toContain("/repo/packages/CONTEXT.md");
|
|
102
89
|
});
|
|
103
90
|
|
|
104
91
|
it("#given a Hephaestus static rule #when formatting SessionStart context #then it injects its body before other rule bodies", () => {
|
|
105
92
|
// given
|
|
106
93
|
const rules = [
|
|
107
|
-
loadedRule({ path: "/repo/alpha.md", relativePath: "alpha.md", body: "
|
|
94
|
+
loadedRule({ path: "/repo/alpha.md", relativePath: "alpha.md", body: "ALPHA_BODY_SENTINEL" }),
|
|
108
95
|
loadedRule({
|
|
109
96
|
path: "/repo/bundled-rules/hephaestus.md",
|
|
110
97
|
relativePath: "bundled-rules/hephaestus.md",
|
|
111
|
-
body: "
|
|
98
|
+
body: "HEPHAESTUS_BODY_SENTINEL",
|
|
112
99
|
}),
|
|
113
|
-
loadedRule({ path: "/repo/beta.md", relativePath: "beta.md", body: "
|
|
100
|
+
loadedRule({ path: "/repo/beta.md", relativePath: "beta.md", body: "BETA_BODY_SENTINEL" }),
|
|
114
101
|
];
|
|
115
102
|
|
|
116
103
|
// when
|
|
117
104
|
const block = formatStaticBlock(rules, FORMAT_OPTIONS);
|
|
118
105
|
|
|
119
106
|
// then
|
|
120
|
-
expect(block).toContain(
|
|
121
|
-
expect(block).toContain("
|
|
122
|
-
expect(block).toContain("
|
|
123
|
-
expect(block).toContain("
|
|
124
|
-
expect(block.indexOf("
|
|
125
|
-
expect(block.indexOf("
|
|
126
|
-
expect(block).not.toContain("must read project rules:");
|
|
127
|
-
expect(block).not.toContain("- [hephaestus.md]");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("#given only a Hephaestus static rule #when formatting SessionStart context #then it emits no project rule link section", () => {
|
|
131
|
-
// given
|
|
132
|
-
const rule = loadedRule({
|
|
133
|
-
path: "/repo/bundled-rules/hephaestus.md",
|
|
134
|
-
relativePath: "bundled-rules/hephaestus.md",
|
|
135
|
-
body: "Hephaestus guidance.",
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// when
|
|
139
|
-
const block = formatStaticBlock([rule], FORMAT_OPTIONS);
|
|
140
|
-
|
|
141
|
-
// then
|
|
142
|
-
expect(block).toContain("Instructions from: /repo/bundled-rules/hephaestus.md");
|
|
143
|
-
expect(block).toContain("Hephaestus guidance.");
|
|
144
|
-
expect(block).not.toContain("- [hephaestus.md]");
|
|
145
|
-
expect(block).not.toContain("must read project rules:");
|
|
107
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/bundled-rules/hephaestus.md`);
|
|
108
|
+
expect(block).toContain("HEPHAESTUS_BODY_SENTINEL");
|
|
109
|
+
expect(block).toContain("ALPHA_BODY_SENTINEL");
|
|
110
|
+
expect(block).toContain("BETA_BODY_SENTINEL");
|
|
111
|
+
expect(block.indexOf("HEPHAESTUS_BODY_SENTINEL")).toBeLessThan(block.indexOf("ALPHA_BODY_SENTINEL"));
|
|
112
|
+
expect(block.indexOf("ALPHA_BODY_SENTINEL")).toBeLessThan(block.indexOf("BETA_BODY_SENTINEL"));
|
|
146
113
|
});
|
|
147
114
|
|
|
148
115
|
it("#given an oversized Hephaestus static rule #when formatting under a tight result budget #then its body is never truncated", () => {
|
|
@@ -162,7 +129,6 @@ describe("rules formatter hook context", () => {
|
|
|
162
129
|
|
|
163
130
|
// then
|
|
164
131
|
expect(block).toContain(tailMarker);
|
|
165
|
-
expect(block).not.toContain("[Truncated. Full:");
|
|
166
132
|
});
|
|
167
133
|
|
|
168
134
|
it("#given multiple oversized rules #when formatting under a tight result budget #then every rule receives a fair truncated share with a read-full guide", () => {
|
|
@@ -180,13 +146,15 @@ describe("rules formatter hook context", () => {
|
|
|
180
146
|
});
|
|
181
147
|
|
|
182
148
|
// then
|
|
183
|
-
expect(block).toContain(
|
|
184
|
-
expect(block).toContain(
|
|
185
|
-
expect(block).toContain(
|
|
186
|
-
expect(block).toContain("
|
|
187
|
-
expect(block).toContain("
|
|
188
|
-
expect(block).toContain("
|
|
189
|
-
expect(
|
|
149
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/alpha.md`);
|
|
150
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/beta.md`);
|
|
151
|
+
expect(block).toContain(`${INSTRUCTIONS_FROM}/repo/gamma.md`);
|
|
152
|
+
expect(block).toContain("alpha-");
|
|
153
|
+
expect(block).toContain("beta-");
|
|
154
|
+
expect(block).toContain("gamma-");
|
|
155
|
+
expect(block).not.toContain("A".repeat(400));
|
|
156
|
+
expect(block).not.toContain("B".repeat(400));
|
|
157
|
+
expect(block).not.toContain("C".repeat(400));
|
|
190
158
|
});
|
|
191
159
|
|
|
192
160
|
it("#given no matching rules #when formatting hook context #then it emits no context", () => {
|
|
@@ -5,14 +5,14 @@ import { formatAdditionalContextOutput } from "../src/hook-output.js";
|
|
|
5
5
|
describe("formatAdditionalContextOutput", () => {
|
|
6
6
|
it("#given context with outer whitespace and CRLF #when serializing hook JSON #then additional context is newline-normalized", () => {
|
|
7
7
|
// given
|
|
8
|
-
const context = "\r\n\r\
|
|
8
|
+
const context = "\r\n\r\nA1\r\nB2\rC3\r\n";
|
|
9
9
|
|
|
10
10
|
// when
|
|
11
11
|
const output = formatAdditionalContextOutput("PostToolUse", context);
|
|
12
12
|
const parsed: unknown = JSON.parse(output);
|
|
13
13
|
|
|
14
14
|
// then
|
|
15
|
-
expect(readAdditionalContext(parsed)).toBe("
|
|
15
|
+
expect(readAdditionalContext(parsed)).toBe("A1\nB2\nC3");
|
|
16
16
|
expect(output.endsWith("\n")).toBe(true);
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -40,7 +40,6 @@ describe("formatAdditionalContextOutput", () => {
|
|
|
40
40
|
expect(additionalContext.length).toBeLessThanOrEqual(32_000);
|
|
41
41
|
expect(additionalContext).toContain("first");
|
|
42
42
|
expect(additionalContext).not.toContain("last");
|
|
43
|
-
expect(additionalContext).toContain("[Truncated hook additional context to 32000 chars");
|
|
44
43
|
});
|
|
45
44
|
});
|
|
46
45
|
|
package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts
CHANGED
|
@@ -71,25 +71,11 @@ describe("Windows Git Bash bundled rule content", () => {
|
|
|
71
71
|
expect(body).toContain("alwaysApply: true");
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it(
|
|
75
|
-
const body = readWindowsRuleBody();
|
|
76
|
-
|
|
77
|
-
expect(body).not.toContain('shell: "bash"');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("#given the bundled rule #when read #then it warns that bare bash often resolves to WSL System32", () => {
|
|
81
|
-
const body = readWindowsRuleBody();
|
|
82
|
-
|
|
83
|
-
expect(body).toMatch(/WSL/i);
|
|
84
|
-
expect(body).toMatch(/System32/i);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("#given the bundled rule #when read #then it prefers the git_bash MCP and the absolute Git Bash path", () => {
|
|
74
|
+
it("#given the bundled rule #when read #then it names the Git Bash runtime identifiers", () => {
|
|
88
75
|
const body = readWindowsRuleBody();
|
|
89
76
|
|
|
90
77
|
expect(body).toContain("git_bash");
|
|
91
78
|
expect(body).toContain("OMO_CODEX_GIT_BASH_PATH");
|
|
92
|
-
expect(body).toContain("C:\\Program Files\\Git\\bin\\bash.exe");
|
|
93
79
|
});
|
|
94
80
|
});
|
|
95
81
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"type": "command",
|
|
8
8
|
"command": "node \"${PLUGIN_ROOT}/components/start-work-continuation/dist/cli.js\" hook stop",
|
|
9
9
|
"timeout": 10,
|
|
10
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
10
|
+
"statusMessage": "(OmO 5.0.0-beta.9) Checking Start-Work Continuation"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"type": "command",
|
|
20
20
|
"command": "node \"${PLUGIN_ROOT}/components/start-work-continuation/dist/cli.js\" hook subagent-stop",
|
|
21
21
|
"timeout": 10,
|
|
22
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
22
|
+
"statusMessage": "(OmO 5.0.0-beta.9) Checking Start-Work Continuation"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -26,9 +26,6 @@ describe("start-work continuation CLI", () => {
|
|
|
26
26
|
expect(result.status).toBe(0);
|
|
27
27
|
const output = parseStopHookOutput(result.stdout);
|
|
28
28
|
expect(output.decision).toBe("block");
|
|
29
|
-
expect(output.reason).toContain("Remaining top-level checkboxes: `2` of `4`");
|
|
30
|
-
expect(output.reason).toContain("Next incomplete task: `1. Implement checklist parser parity`");
|
|
31
|
-
expect(output.reason).toContain("Your session id in boulder.json: `codex:s1`");
|
|
32
29
|
});
|
|
33
30
|
|
|
34
31
|
it("#given valid SubagentStop stdin #when CLI runs #then stdout contains block JSON", () => {
|
package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts
CHANGED
|
@@ -58,16 +58,7 @@ describe("start-work Stop hook", () => {
|
|
|
58
58
|
const output = runStopHook(createStopInput(workspace), fs);
|
|
59
59
|
|
|
60
60
|
// then
|
|
61
|
-
|
|
62
|
-
expect(parsed.decision).toBe("block");
|
|
63
|
-
expect(parsed.reason).toContain("- Plan: `launch-plan`");
|
|
64
|
-
expect(parsed.reason).toContain(`- Plan file: \`${join(workspace, ".omo", "plans", "plan.md")}\``);
|
|
65
|
-
expect(parsed.reason).toContain(`- Boulder state: \`${join(workspace, ".omo", "boulder.json")}\``);
|
|
66
|
-
expect(parsed.reason).toContain("- Remaining top-level checkboxes: `2` of `4`");
|
|
67
|
-
expect(parsed.reason).toContain("- Next incomplete task: `1. Implement checklist parser parity`");
|
|
68
|
-
expect(parsed.reason).toContain("- Worktree: `/tmp/worktree`");
|
|
69
|
-
expect(parsed.reason).toContain(`- Ledger: \`${join(workspace, ".omo", "start-work", "ledger.jsonl")}\``);
|
|
70
|
-
expect(parsed.reason).toContain("- Your session id in boulder.json: `codex:sess_abc`");
|
|
61
|
+
expect(parseBlockOutput(output).decision).toBe("block");
|
|
71
62
|
});
|
|
72
63
|
|
|
73
64
|
it("#given active codex work and an external-blocker marker #when hook runs #then returns empty output", () => {
|
|
@@ -187,12 +178,7 @@ describe("start-work Stop hook", () => {
|
|
|
187
178
|
const output = runStopHook(createStopInput(workspace), fs);
|
|
188
179
|
|
|
189
180
|
// then
|
|
190
|
-
|
|
191
|
-
expect(parsed.decision).toBe("block");
|
|
192
|
-
expect(parsed.reason).toContain("- Remaining top-level checkboxes: `0` of `1`");
|
|
193
|
-
expect(parsed.reason).toContain("- Next incomplete task: `none (final gate pending)`");
|
|
194
|
-
expect(parsed.reason).toContain("When the remaining count is `0`, skip checkbox execution");
|
|
195
|
-
expect(parsed.reason).toContain("re-read the ledger record and verify the exact lane/SHA pair");
|
|
181
|
+
expect(parseBlockOutput(output).decision).toBe("block");
|
|
196
182
|
});
|
|
197
183
|
|
|
198
184
|
it("#given context-window pressure in transcript #when hook runs #then it does not inject continuation text", () => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "command",
|
|
9
9
|
"command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
|
|
10
10
|
"timeout": 10,
|
|
11
|
-
"statusMessage": "(OmO 5.0.0-beta.
|
|
11
|
+
"statusMessage": "(OmO 5.0.0-beta.9) Checking Thread Title Hygiene"
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
14
|
}
|
|
@@ -29,9 +29,7 @@ describe("thread title PostToolUse guidance", () => {
|
|
|
29
29
|
expect(isHookOutput(parsed)).toBe(true);
|
|
30
30
|
if (!isHookOutput(parsed)) return;
|
|
31
31
|
expect(parsed.hookSpecificOutput.hookEventName).toBe("PostToolUse");
|
|
32
|
-
expect(parsed.hookSpecificOutput.additionalContext).
|
|
33
|
-
"THREAD ID thread-123: CALL codex_app.set_thread_title NOW. USE THE REAL TASK/ROLE.",
|
|
34
|
-
);
|
|
32
|
+
expect(parsed.hookSpecificOutput.additionalContext).toContain("thread-123");
|
|
35
33
|
});
|
|
36
34
|
|
|
37
35
|
it("#given Codex reports create_thread output as a JSON string #when the hook runs #then it still extracts the thread id", () => {
|
|
@@ -59,9 +57,7 @@ describe("thread title PostToolUse guidance", () => {
|
|
|
59
57
|
// then
|
|
60
58
|
expect(isHookOutput(parsed)).toBe(true);
|
|
61
59
|
if (!isHookOutput(parsed)) return;
|
|
62
|
-
expect(parsed.hookSpecificOutput.additionalContext).
|
|
63
|
-
"THREAD ID 019ef350-ee78-72a3-bd5e-e40cebc3d814: CALL codex_app.set_thread_title NOW. USE THE REAL TASK/ROLE.",
|
|
64
|
-
);
|
|
60
|
+
expect(parsed.hookSpecificOutput.additionalContext).toContain("019ef350-ee78-72a3-bd5e-e40cebc3d814");
|
|
65
61
|
});
|
|
66
62
|
|
|
67
63
|
it("#given an unrelated tool completed #when the hook runs #then it stays silent", () => {
|
|
@@ -141,9 +137,7 @@ describe("thread title PostToolUse guidance", () => {
|
|
|
141
137
|
// then
|
|
142
138
|
expect(isHookOutput(parsed)).toBe(true);
|
|
143
139
|
if (!isHookOutput(parsed)) return;
|
|
144
|
-
expect(parsed.hookSpecificOutput.additionalContext).
|
|
145
|
-
"PENDING WORKTREE ID remote-control:env:test-worktree: WORKTREE THREAD IS NOT READY YET. DO NOT bind-thread OR SEND THE MEMBER BOOTSTRAP UNTIL A REAL THREAD ID EXISTS. THEN CALL codex_app.set_thread_title USING THE REAL TASK/ROLE.",
|
|
146
|
-
);
|
|
140
|
+
expect(parsed.hookSpecificOutput.additionalContext).toContain("remote-control:env:test-worktree");
|
|
147
141
|
});
|
|
148
142
|
});
|
|
149
143
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-telemetry",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
4
|
"description": "Codex plugin component that emits omo-codex anonymous daily-active telemetry on SessionStart.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/codex-ultrawork",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
4
|
"description": "Codex plugin that injects the ultrawork orchestration directive and ships LazyCodex planning, review, QA, and gate agent roles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.12.1",
|