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.
Files changed (114) hide show
  1. package/.agents/command/publish.md +44 -16
  2. package/.agents/skills/publish/SKILL.md +44 -16
  3. package/.opencode/command/publish.md +44 -16
  4. package/dist/agents/atlas/agent.d.ts +0 -1
  5. package/dist/cli/index.js +34 -31
  6. package/dist/cli/run/on-complete-hook.d.ts +2 -0
  7. package/dist/cli-node/index.js +34 -31
  8. package/dist/hooks/atlas/final-wave-approval-gate.test-support.d.ts +50 -0
  9. package/dist/hooks/atlas/system-reminder-templates.d.ts +0 -1
  10. package/dist/index.js +25 -24
  11. package/dist/shared/shell-env.d.ts +1 -1
  12. package/dist/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
  13. package/dist/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  14. package/dist/tui.js +2 -2
  15. package/package.json +15 -14
  16. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  17. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  18. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +2 -2
  20. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +2 -2
  21. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  23. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  25. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +3 -17
  29. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  30. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  33. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/rules/test/bundled-rules-priority.test.ts +11 -16
  35. package/packages/omo-codex/plugin/components/rules/test/bundled-rules.test.ts +16 -23
  36. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +9 -7
  37. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-context.test.ts +0 -6
  38. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +6 -4
  39. package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-directive.test.ts +12 -9
  40. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +28 -37
  41. package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +37 -69
  42. package/packages/omo-codex/plugin/components/rules/test/hook-output.test.ts +2 -3
  43. package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +1 -15
  44. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  45. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +0 -3
  47. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +2 -16
  48. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  49. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  50. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +3 -9
  51. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  52. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  54. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +0 -136
  56. package/packages/omo-codex/plugin/components/ultrawork/test/skill-pointer.test.ts +0 -2
  57. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  58. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-continuation.test.ts +0 -1
  60. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +2 -2
  61. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +0 -3
  62. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +2 -35
  63. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +4 -5
  64. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  87. package/packages/omo-codex/plugin/package-lock.json +20 -20
  88. package/packages/omo-codex/plugin/package.json +1 -1
  89. package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
  90. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  91. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +19 -173
  92. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +4 -24
  93. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +175 -13
  94. package/packages/omo-codex/plugin/test/aggregate.test.mjs +78 -2
  95. package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +19 -33
  96. package/packages/omo-codex/plugin/test/lcx-contribute-bug-fix-template.test.mjs +21 -27
  97. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +0 -36
  98. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +101 -0
  99. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +1 -119
  100. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +0 -40
  101. package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +6 -62
  102. package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +3 -36
  103. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +0 -44
  104. package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +2 -6
  105. package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +0 -3
  106. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +0 -3
  107. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  108. package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +7 -2
  109. package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +35 -8
  110. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +110 -0
  111. package/dist/tools/call-omo-agent/background-agent-executor.d.ts +0 -5
  112. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +0 -92
  113. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +0 -314
  114. 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"), "Project AGENTS.md should stay Codex-native.");
92
- writeFileSync(path.join(root, "CLAUDE.md"), "Project CLAUDE.md should stay outside rules hook context.");
93
- writeFileSync(path.join(root, "CONTEXT.md"), "Always wear safety goggles when refactoring.");
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
- `Instructions from: ${path.join(root, "CONTEXT.md")}`,
225
+ `${INSTRUCTIONS_FROM}${path.join(root, "CONTEXT.md")}`,
230
226
  );
231
- expect(parsed.hookSpecificOutput?.additionalContext).toContain("Always wear safety goggles");
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("## Project Instructions");
246
- expect(parsed.hookSpecificOutput?.additionalContext).not.toContain("Project AGENTS.md should stay Codex-native.");
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("## Project Instructions");
324
- expect(clearContext).toContain(`Instructions from: ${path.join(root, "CONTEXT.md")}`);
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
- "Additional project instructions matched for src/app.ts",
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("Additional project instructions matched for src/app.ts");
392
+ expect(additionalContext).toContain("src/app.ts");
400
393
  expect(additionalContext).not.toContain("src\\app.ts");
401
- expect(occurrenceCount(additionalContext, "Prefer strict TypeScript")).toBe(1);
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("based on GPT-5.5");
441
- expect(firstContext).not.toContain("based on GPT-5.6");
442
- expect(secondContext).toContain("based on GPT-5.6");
443
- expect(secondContext).not.toContain("based on GPT-5.5");
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("Prefer strict TypeScript");
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"', "Prefer readonly TypeScript after rule edits.");
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("Prefer strict TypeScript");
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: ["# packages", "", "## OVERVIEW", "23 sibling packages.", "", "## CONVENTIONS", "Use npm."].join("\n"),
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).toBe(
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: "Keep generated hook context readable.",
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).toBe(
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: "First line\r\n indented second line\rThird line",
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("First line\n indented second line\nThird line");
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: "Shared rule\nKeep one copy.",
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: "Shared rule\r\nKeep one copy.",
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, "Instructions from: /repo/CONTEXT.md")).toBe(1);
100
- expect(occurrenceCount(block, "Shared rule\nKeep one copy.")).toBe(1);
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: "Alpha guidance." }),
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: "Hephaestus guidance.",
98
+ body: "HEPHAESTUS_BODY_SENTINEL",
112
99
  }),
113
- loadedRule({ path: "/repo/beta.md", relativePath: "beta.md", body: "Beta guidance." }),
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("Instructions from: /repo/bundled-rules/hephaestus.md");
121
- expect(block).toContain("Hephaestus guidance.");
122
- expect(block).toContain("Alpha guidance.");
123
- expect(block).toContain("Beta guidance.");
124
- expect(block.indexOf("Hephaestus guidance.")).toBeLessThan(block.indexOf("Alpha guidance."));
125
- expect(block.indexOf("Alpha guidance.")).toBeLessThan(block.indexOf("Beta guidance."));
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("Instructions from: /repo/alpha.md");
184
- expect(block).toContain("Instructions from: /repo/beta.md");
185
- expect(block).toContain("Instructions from: /repo/gamma.md");
186
- expect(block).toContain("[Truncated. Full: alpha.md]");
187
- expect(block).toContain("[Truncated. Full: beta.md]");
188
- expect(block).toContain("[Truncated. Full: gamma.md]");
189
- expect(occurrenceCount(block, "[Truncated. Full:")).toBe(3);
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\nFirst line\r\nSecond line\rThird line\r\n";
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("First line\nSecond line\nThird line");
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
 
@@ -71,25 +71,11 @@ describe("Windows Git Bash bundled rule content", () => {
71
71
  expect(body).toContain("alwaysApply: true");
72
72
  });
73
73
 
74
- it('#given the bundled rule #when read #then it NEVER recommends bare shell:"bash"', () => {
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.8) Checking Start-Work Continuation"
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.8) Checking Start-Work Continuation"
22
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking Start-Work Continuation"
23
23
  }
24
24
  ]
25
25
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-start-work-continuation",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex Stop hook continuation injector for omo-codex start-work plans.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -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", () => {
@@ -58,16 +58,7 @@ describe("start-work Stop hook", () => {
58
58
  const output = runStopHook(createStopInput(workspace), fs);
59
59
 
60
60
  // then
61
- const parsed = parseBlockOutput(output);
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
- const parsed = parseBlockOutput(output);
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.8) Checking Thread Title Hygiene"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking Thread Title Hygiene"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-teammode",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex team-mode hook component that keeps background thread titles descriptive after create_thread.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -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).toBe(
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).toBe(
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).toBe(
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
 
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.8) Recording Session Telemetry"
10
+ "statusMessage": "(OmO 5.0.0-beta.9) Recording Session Telemetry"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-telemetry",
3
- "version": "5.0.0-beta.8",
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",
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.0.0-beta.8) Checking Ultrawork Trigger"
10
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking Ultrawork Trigger"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ultrawork",
3
- "version": "5.0.0-beta.8",
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",