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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "One Codex plugin namespace for Yeongyu's local Codex components.",
5
5
  "author": {
6
6
  "name": "Yeongyu Kim",
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.8) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-bootstrap",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex SessionStart bootstrap component that provisions LazyCodex runtime dependencies from a detached worker.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -4003,7 +4003,7 @@ function finalize(ctx, schema) {
4003
4003
  result.$schema = "http://json-schema.org/draft-07/schema#";
4004
4004
  } else if (ctx.target === "draft-04") {
4005
4005
  result.$schema = "http://json-schema.org/draft-04/schema#";
4006
- } else if (ctx.target === "openapi-3.0") {} else {}
4006
+ } else if (ctx.target === "openapi-3.0") {}
4007
4007
  if (ctx.external?.uri) {
4008
4008
  const id = ctx.external.registry.get(schema)?.id;
4009
4009
  if (!id)
@@ -4212,7 +4212,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
4212
4212
  if (val === undefined) {
4213
4213
  if (ctx.unrepresentable === "throw") {
4214
4214
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
4215
- } else {}
4215
+ }
4216
4216
  } else if (typeof val === "bigint") {
4217
4217
  if (ctx.unrepresentable === "throw") {
4218
4218
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -4269,7 +4269,7 @@ function finalize(ctx, schema) {
4269
4269
  result.$schema = "http://json-schema.org/draft-07/schema#";
4270
4270
  } else if (ctx.target === "draft-04") {
4271
4271
  result.$schema = "http://json-schema.org/draft-04/schema#";
4272
- } else if (ctx.target === "openapi-3.0") {} else {}
4272
+ } else if (ctx.target === "openapi-3.0") {}
4273
4273
  if (ctx.external?.uri) {
4274
4274
  const id = ctx.external.registry.get(schema)?.id;
4275
4275
  if (!id)
@@ -4478,7 +4478,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
4478
4478
  if (val === undefined) {
4479
4479
  if (ctx.unrepresentable === "throw") {
4480
4480
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
4481
- } else {}
4481
+ }
4482
4482
  } else if (typeof val === "bigint") {
4483
4483
  if (ctx.unrepresentable === "throw") {
4484
4484
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-codegraph",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex plugin MCP wrapper for CodeGraph.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.0.0-beta.8) Checking Comments"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking Comments"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-comment-checker",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex plugin that runs comment-checker after edit-like PostToolUse hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.0.0-beta.8) Recommending Git Bash MCP"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Recommending Git Bash MCP"
12
12
  }
13
13
  ]
14
14
  }
@@ -20,7 +20,7 @@
20
20
  "type": "command",
21
21
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
22
22
  "timeout": 5,
23
- "statusMessage": "(OmO 5.0.0-beta.8) Resetting Git Bash MCP Reminder"
23
+ "statusMessage": "(OmO 5.0.0-beta.9) Resetting Git Bash MCP Reminder"
24
24
  }
25
25
  ]
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-git-bash-hook",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.0.0-beta.8) Verifying LazyCodex Executor Evidence"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Verifying LazyCodex Executor Evidence"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex SubagentStop evidence verifier for LazyCodex executor completions.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -36,10 +36,8 @@ describe("lazycodex executor SubagentStop verifier", () => {
36
36
  // then
37
37
  const parsed = parseBlockOutput(output);
38
38
  expect(parsed.decision).toBe("block");
39
- expect(parsed.reason).toContain("너는 방금 작업을 완료했다고 보고했고, 그건 거짓말이다.");
40
39
  expect(parsed.reason).toContain(".omo/evidence/");
41
40
  expect(parsed.reason).toContain("EVIDENCE_RECORDED: <path>");
42
- expect(parsed.reason).not.toContain("2번째");
43
41
  });
44
42
 
45
43
  it("#given a prior blocked stop #when lazycodex executor stops again #then escalates the attempt count", () => {
@@ -51,8 +49,9 @@ describe("lazycodex executor SubagentStop verifier", () => {
51
49
  const output = runSubagentStopHook(createInput(cwd), nodeFileSystem);
52
50
 
53
51
  // then
54
- const parsed = parseBlockOutput(output);
55
- expect(parsed.reason).toContain("2번째");
52
+ expect(parseBlockOutput(output).decision).toBe("block");
53
+ const statePath = join(cwd, ".omo", "lazycodex-executor-verify", "sess.1-agent_1.json");
54
+ expect(JSON.parse(readFileSync(statePath, "utf8"))).toEqual({ attempts: 2 });
56
55
  });
57
56
 
58
57
  it("#given turn_id is omitted #when lazycodex executor stops #then the hook still parses the payload", () => {
@@ -394,18 +393,5 @@ describe("tier worker receipt enforcement", () => {
394
393
  expect(matcher.test("lazycodex-gate-reviewer")).toBe(false);
395
394
  }
396
395
  });
397
-
398
- it("#given the tier worker TOMLs #when inspected #then each instructs the EVIDENCE_RECORDED receipt line", () => {
399
- for (const tier of ["low", "medium", "high"]) {
400
- // when
401
- const toml = readFileSync(
402
- new URL(`../../ultrawork/agents/lazycodex-worker-${tier}.toml`, import.meta.url),
403
- "utf8",
404
- );
405
-
406
- // then
407
- expect(toml).toContain("EVIDENCE_RECORDED: <path>");
408
- }
409
- });
410
396
  });
411
397
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "5.0.0-beta.8",
4
- "inputDigest": "sha256:9843f0a9d8a8ddb5d8bf2d8da3219fe9033d84f43db3e8a534a908833bc7836a",
3
+ "version": "5.0.0-beta.9",
4
+ "inputDigest": "sha256:ee9f0b367eba57b8d9cd84cca99311490d21a41b92331958919a5f289b428f2c",
5
5
  "outputs": [
6
6
  {
7
7
  "path": "cli.d.ts",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 60,
11
- "statusMessage": "(OmO 5.0.0-beta.8) Checking LSP Diagnostics"
11
+ "statusMessage": "(OmO 5.0.0-beta.9) Checking LSP Diagnostics"
12
12
  }
13
13
  ]
14
14
  }
@@ -21,7 +21,7 @@
21
21
  "type": "command",
22
22
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
23
23
  "timeout": 5,
24
- "statusMessage": "(OmO 5.0.0-beta.8) Resetting LSP Diagnostics Cache"
24
+ "statusMessage": "(OmO 5.0.0-beta.9) Resetting LSP Diagnostics Cache"
25
25
  }
26
26
  ]
27
27
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lsp",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex plugin that exposes Language Server Protocol tools and post-edit diagnostics.",
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 session-start",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.0.0-beta.8) Loading Project Rules"
10
+ "statusMessage": "(OmO 5.0.0-beta.9) Loading Project Rules"
11
11
  }
12
12
  ]
13
13
  }
@@ -19,7 +19,7 @@
19
19
  "type": "command",
20
20
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
21
21
  "timeout": 10,
22
- "statusMessage": "(OmO 5.0.0-beta.8) Loading Project Rules"
22
+ "statusMessage": "(OmO 5.0.0-beta.9) Loading Project Rules"
23
23
  }
24
24
  ]
25
25
  }
@@ -32,7 +32,7 @@
32
32
  "type": "command",
33
33
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
34
34
  "timeout": 10,
35
- "statusMessage": "(OmO 5.0.0-beta.8) Matching Project Rules"
35
+ "statusMessage": "(OmO 5.0.0-beta.9) Matching Project Rules"
36
36
  }
37
37
  ]
38
38
  }
@@ -45,7 +45,7 @@
45
45
  "type": "command",
46
46
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
47
47
  "timeout": 10,
48
- "statusMessage": "(OmO 5.0.0-beta.8) Resetting Project Rule Cache"
48
+ "statusMessage": "(OmO 5.0.0-beta.9) Resetting Project Rule Cache"
49
49
  }
50
50
  ]
51
51
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-rules",
3
- "version": "5.0.0-beta.8",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Codex plugin that injects project rule files into model context through lifecycle hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,19 +1,22 @@
1
1
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import type { RuleCandidate } from "@oh-my-opencode/rules-engine/engine";
5
+ import {
6
+ createEngine,
7
+ defaultConfig,
8
+ type EngineDeps,
9
+ resolvePluginRulesRoot,
10
+ SOURCE_PRIORITY,
11
+ } from "@oh-my-opencode/rules-engine/engine";
4
12
  import { afterEach, describe, expect, it } from "vitest";
5
-
6
13
  import { configFromEnvironment } from "../src/config.js";
7
- import { SOURCE_PRIORITY } from "@oh-my-opencode/rules-engine/engine";
8
- import { createEngine, defaultConfig, type EngineDeps } from "@oh-my-opencode/rules-engine/engine";
9
- import { resolvePluginRulesRoot } from "@oh-my-opencode/rules-engine/engine";
10
- import type { RuleCandidate } from "@oh-my-opencode/rules-engine/engine";
11
14
 
12
15
  const projectRoot = "/tmp/codex-rules-bundled-priority";
13
16
  const bundledPath = join(projectRoot, "bundled-rules", "hephaestus.md");
14
17
  const homePath = join(projectRoot, "home", ".opencode", "rules", "hephaestus.md");
15
- const bundledBody = "Bundled baseline discipline.";
16
- const homeBody = "Home baseline discipline override.";
18
+ const bundledBody = "BUNDLED_BASELINE_SENTINEL";
19
+ const homeBody = "HOME_OVERRIDE_SENTINEL";
17
20
  const tempDirectories: string[] = [];
18
21
 
19
22
  afterEach(() => {
@@ -35,14 +38,7 @@ function globalCandidate(source: "plugin-bundled" | "~/.opencode/rules", path: s
35
38
  }
36
39
 
37
40
  function ruleMarkdown(body: string): string {
38
- return [
39
- "---",
40
- "description: OMO Hephaestus baseline discipline for Codex",
41
- "alwaysApply: true",
42
- "---",
43
- "",
44
- body,
45
- ].join("\n");
41
+ return ["---", "description: Fixture", "alwaysApply: true", "---", "", body].join("\n");
46
42
  }
47
43
 
48
44
  describe("plugin bundled rule priority", () => {
@@ -85,7 +81,6 @@ describe("plugin bundled rule priority", () => {
85
81
  // then
86
82
  expect(formatted).toContain(homePath);
87
83
  expect(formatted).toContain(homeBody);
88
- expect(formatted).not.toContain(`- [hephaestus.md]{${homePath}}`);
89
84
  expect(formatted).not.toContain(bundledPath);
90
85
  expect(formatted).not.toContain(bundledBody);
91
86
  });
@@ -40,8 +40,12 @@ const DISABLED_BUNDLED_ENV = {
40
40
  CODEX_RULES_DISABLE_BUNDLED: "1",
41
41
  };
42
42
 
43
- const BUNDLED_BODY = "Bundled craftsman baseline.";
44
- const SHARED_BODY = "Always choose the smallest correct change.";
43
+ // Runtime sentinel: the rules hook emits and re-greps this marker when
44
+ // deduplicating injected rules against the transcript (transcript-rule-filter.ts).
45
+ const INSTRUCTIONS_FROM = "Instructions from: ";
46
+
47
+ const BUNDLED_BODY = "BUNDLED_PERSONA_SENTINEL";
48
+ const SHARED_BODY = "SHARED_RULE_SENTINEL";
45
49
 
46
50
  const tempDirectories: string[] = [];
47
51
  let originalPluginRoot: string | undefined;
@@ -130,10 +134,6 @@ function userPromptSubmitInput(root: string): Parameters<typeof runUserPromptSub
130
134
  };
131
135
  }
132
136
 
133
- function occurrenceCount(value: string, search: string): number {
134
- return value.split(search).length - 1;
135
- }
136
-
137
137
  describe("plugin bundled rules", () => {
138
138
  it("#given PLUGIN_ROOT with bundled markdown #when finding candidates #then plugin-bundled source is cached", () => {
139
139
  // given
@@ -162,11 +162,8 @@ describe("plugin bundled rules", () => {
162
162
 
163
163
  // then
164
164
  expect(output).toContain('"hookEventName":"SessionStart"');
165
- expect(output).toContain(`Instructions from: ${bundledRulePath}`);
165
+ expect(output).toContain(`${INSTRUCTIONS_FROM}${bundledRulePath}`);
166
166
  expect(output).toContain(BUNDLED_BODY);
167
- expect(output).toContain("## Project Instructions");
168
- expect(output).not.toContain("must read project rules:");
169
- expect(output).not.toContain(`- [hephaestus.md]{${bundledRulePath}}`);
170
167
  });
171
168
 
172
169
  it("#given same project and bundled body #when SessionStart runs #then project rule file wins", async () => {
@@ -180,8 +177,7 @@ describe("plugin bundled rules", () => {
180
177
  });
181
178
 
182
179
  // then
183
- expect(occurrenceCount(output, "- [hephaestus.md]{")).toBe(0);
184
- expect(output).toContain(`Instructions from: ${projectRulePath}`);
180
+ expect(output).toContain(`${INSTRUCTIONS_FROM}${projectRulePath}`);
185
181
  expect(output).toContain(SHARED_BODY);
186
182
  expect(output).not.toContain(bundledRulePath);
187
183
  });
@@ -207,7 +203,7 @@ describe("plugin bundled rules", () => {
207
203
  pluginDataRoot: pluginData,
208
204
  env: BUNDLED_ONLY_ENV,
209
205
  });
210
- expect(firstOutput).toContain(`Instructions from: ${bundledRulePath}`);
206
+ expect(firstOutput).toContain(`${INSTRUCTIONS_FROM}${bundledRulePath}`);
211
207
  expect(firstOutput).toContain(BUNDLED_BODY);
212
208
 
213
209
  // when
@@ -219,9 +215,8 @@ describe("plugin bundled rules", () => {
219
215
 
220
216
  // then
221
217
  expect(compactOutput).toBe("");
222
- expect(output).toContain(`Instructions from: ${bundledRulePath}`);
218
+ expect(output).toContain(`${INSTRUCTIONS_FROM}${bundledRulePath}`);
223
219
  expect(output).toContain(BUNDLED_BODY);
224
- expect(output).not.toContain("[Truncated. Full:");
225
220
  });
226
221
 
227
222
  it("#given bundled Hephaestus rule body exceeds per-rule cap #when SessionStart runs #then static context expands the body within result budget", async () => {
@@ -232,7 +227,7 @@ describe("plugin bundled rules", () => {
232
227
  tempDirectories.push(root, pluginRoot, pluginData);
233
228
  writeFileSync(join(root, "package.json"), JSON.stringify({ name: "fixture" }));
234
229
  mkdirSync(join(pluginRoot, "bundled-rules"), { recursive: true });
235
- const oversizedBody = "The bundled craftsman discipline is non-negotiable. ".repeat(400);
230
+ const oversizedBody = "BUNDLED_OVERSIZE_SENTINEL_".repeat(500);
236
231
  expect(oversizedBody.length).toBeGreaterThan(12000);
237
232
  const tailMarker = "BUNDLED_TAIL_SENTINEL_LANDS_IN_FULL";
238
233
  const bundledRulePath = join(pluginRoot, "bundled-rules", "hephaestus.md");
@@ -247,10 +242,9 @@ describe("plugin bundled rules", () => {
247
242
  });
248
243
 
249
244
  // then
250
- expect(output).toContain(`Instructions from: ${bundledRulePath}`);
251
- expect(output).toContain("The bundled craftsman discipline is non-negotiable.");
245
+ expect(output).toContain(`${INSTRUCTIONS_FROM}${bundledRulePath}`);
246
+ expect(output).toContain("BUNDLED_OVERSIZE_SENTINEL_");
252
247
  expect(output).toContain(tailMarker);
253
- expect(output).not.toContain("[Truncated. Full:");
254
248
  });
255
249
 
256
250
  it("#given project rule body exceeds per-rule cap #when SessionStart runs #then static context injects a truncated body", async () => {
@@ -262,7 +256,7 @@ describe("plugin bundled rules", () => {
262
256
  writeFileSync(join(root, "package.json"), JSON.stringify({ name: "fixture" }));
263
257
  mkdirSync(join(root, ".omo", "rules"), { recursive: true });
264
258
  mkdirSync(join(pluginRoot, "bundled-rules"), { recursive: true });
265
- const oversizedBody = "The project rule body is intentionally oversized for the cap test. ".repeat(300);
259
+ const oversizedBody = "PROJECT_OVERSIZE_SENTINEL_".repeat(500);
266
260
  expect(oversizedBody.length).toBeGreaterThan(12000);
267
261
  const tailMarker = "PROJECT_TAIL_SENTINEL_SHOULD_NOT_LAND";
268
262
  const projectRulePath = join(root, ".omo", "rules", "oversized.md");
@@ -277,9 +271,8 @@ describe("plugin bundled rules", () => {
277
271
  });
278
272
 
279
273
  // then
280
- expect(output).toContain(`Instructions from: ${projectRulePath}`);
281
- expect(output).toContain("The project rule body is intentionally oversized for the cap test.");
282
- expect(output).toContain("[Truncated. Full:");
274
+ expect(output).toContain(`${INSTRUCTIONS_FROM}${projectRulePath}`);
275
+ expect(output).toContain("PROJECT_OVERSIZE_SENTINEL_");
283
276
  expect(output).not.toContain(tailMarker);
284
277
  });
285
278
  });
@@ -18,6 +18,10 @@ const PROJECT_RULES_ENV = {
18
18
  CODEX_RULES_MAX_RULE_CHARS: "30000",
19
19
  };
20
20
 
21
+ // Runtime sentinel: the rules hook emits and re-greps this marker when
22
+ // deduplicating injected rules against the transcript (transcript-rule-filter.ts).
23
+ const INSTRUCTIONS_FROM = "Instructions from: ";
24
+
21
25
  afterEach(() => {
22
26
  for (const directory of tempDirectories.splice(0)) {
23
27
  rmSync(directory, { recursive: true, force: true });
@@ -46,15 +50,13 @@ describe("codex rules post-compaction context budget", () => {
46
50
  });
47
51
 
48
52
  // then
49
- expect(firstContext).toContain(`Instructions from: ${path.join(root, "CONTEXT.md")}`);
50
- expect(firstContext).toContain("Project rule");
51
- expect(firstContext).toContain("[Truncated. Full:");
53
+ expect(firstContext).toContain(`${INSTRUCTIONS_FROM}${path.join(root, "CONTEXT.md")}`);
54
+ expect(firstContext).toContain("CTX_OVERSIZE_SENTINEL");
55
+ expect(firstContext).not.toContain("CTX_TAIL_SENTINEL");
52
56
  expect(firstContext.length).toBeLessThan(31_000);
53
57
  const recoveryContext = readAdditionalContext(output);
54
- expect(recoveryContext).toContain("MUST READ");
55
- expect(recoveryContext).toContain("NO EXCUSES");
56
58
  expect(recoveryContext).toContain(path.join(root, "CONTEXT.md"));
57
- expect(recoveryContext).not.toContain("Project rule");
59
+ expect(recoveryContext).not.toContain("CTX_OVERSIZE_SENTINEL");
58
60
  expect(recoveryContext.length).toBeLessThan(2_000);
59
61
  });
60
62
  });
@@ -66,7 +68,7 @@ function makeOversizedProject(): { root: string; pluginData: string } {
66
68
  writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
67
69
  writeFileSync(path.join(root, "AGENTS.md"), "Project AGENTS.md should stay Codex-native.");
68
70
  writeFileSync(path.join(root, "CLAUDE.md"), "Project CLAUDE.md should stay outside rules hook context.");
69
- writeFileSync(path.join(root, "CONTEXT.md"), `Project rule\n${"A".repeat(30_000)}`);
71
+ writeFileSync(path.join(root, "CONTEXT.md"), `CTX_OVERSIZE_SENTINEL\n${"A".repeat(30_000)}\nCTX_TAIL_SENTINEL`);
70
72
  mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
71
73
  writeFileSync(
72
74
  path.join(root, ".omo", "rules", "typescript.md"),
@@ -38,7 +38,6 @@ describe("codex rules compacted context recovery", () => {
38
38
  // then
39
39
  const postCompactContext = readAdditionalContext(output);
40
40
  expect(postCompactContext.length).toBeLessThan(2_000);
41
- expect(postCompactContext).toContain("MUST READ");
42
41
  expect(postCompactContext).toContain("CONTEXT.md");
43
42
  expect(postCompactContext).not.toContain("Project rule");
44
43
  });
@@ -65,7 +64,6 @@ describe("codex rules compacted context recovery", () => {
65
64
  // then
66
65
  const firstContext = readOptionalAdditionalContext(firstOutput);
67
66
  expect(firstContext.length).toBeLessThan(1_000);
68
- expect(firstContext).toContain("MUST READ");
69
67
  expect(firstContext).toContain("CONTEXT.md");
70
68
  expect(secondOutput).toBe("");
71
69
  });
@@ -88,7 +86,6 @@ describe("codex rules compacted context recovery", () => {
88
86
  // then
89
87
  const context = readOptionalAdditionalContext(output);
90
88
  expect(context.length).toBeLessThan(1_000);
91
- expect(context).toContain("MUST READ");
92
89
  expect(context).toContain("CONTEXT.md");
93
90
  });
94
91
 
@@ -106,7 +103,6 @@ describe("codex rules compacted context recovery", () => {
106
103
  // then
107
104
  const context = readOptionalAdditionalContext(output);
108
105
  expect(context.length).toBeLessThan(1_000);
109
- expect(context).toContain("MUST READ");
110
106
  expect(context).toContain("CONTEXT.md");
111
107
  });
112
108
 
@@ -128,7 +124,6 @@ describe("codex rules compacted context recovery", () => {
128
124
  // then
129
125
  const context = readOptionalAdditionalContext(output);
130
126
  expect(context.length).toBeLessThan(1_000);
131
- expect(context).toContain("MUST READ");
132
127
  expect(context).toContain("CONTEXT.md");
133
128
  });
134
129
 
@@ -157,7 +152,6 @@ describe("codex rules compacted context recovery", () => {
157
152
  const contexts = outputs.map(readOptionalAdditionalContext);
158
153
  expect(contexts.filter((context) => context.length > 0)).toHaveLength(1);
159
154
  expect(contexts.join("").length).toBeLessThan(1_000);
160
- expect(contexts.join("")).toContain("MUST READ");
161
155
  expect(contexts.join("")).toContain("CONTEXT.md");
162
156
  });
163
157
  });
@@ -18,6 +18,10 @@ const PROJECT_ONLY_ENV = {
18
18
  CODEX_RULES_ENABLED_SOURCES: "CONTEXT.md,.omo/rules",
19
19
  };
20
20
 
21
+ // Runtime sentinel: the rules hook emits and re-greps this marker when
22
+ // deduplicating injected rules against the transcript (transcript-rule-filter.ts).
23
+ const INSTRUCTIONS_FROM = "Instructions from: ";
24
+
21
25
  afterEach(() => {
22
26
  for (const directory of tempDirectories.splice(0)) {
23
27
  rmSync(directory, { recursive: true, force: true });
@@ -114,9 +118,8 @@ describe("codex rules PostCompact deduplication", () => {
114
118
 
115
119
  // then
116
120
  const context = readAdditionalContext(output);
117
- expect(context).toContain("MUST READ");
118
121
  expect(context).toContain("CONTEXT.md");
119
- expect(context).not.toContain("Instructions from:");
122
+ expect(context).not.toContain(INSTRUCTIONS_FROM);
120
123
  });
121
124
 
122
125
  it("#given startup static context dropped by compaction #when compact SessionStart runs after PostCompact #then it emits a mandatory read directive without rule bodies", async () => {
@@ -140,9 +143,8 @@ describe("codex rules PostCompact deduplication", () => {
140
143
 
141
144
  // then
142
145
  const context = readAdditionalContext(output);
143
- expect(context).toContain("MUST READ");
144
146
  expect(context).toContain("CONTEXT.md");
145
- expect(context).not.toContain("Instructions from:");
147
+ expect(context).not.toContain(INSTRUCTIONS_FROM);
146
148
  });
147
149
  });
148
150
 
@@ -1,8 +1,9 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { parseRule } from "@oh-my-opencode/rules-engine/engine";
4
6
  import { afterEach, describe, expect, it } from "vitest";
5
-
6
7
  import {
7
8
  type CodexPostCompactInput,
8
9
  type CodexPostToolUseInput,
@@ -20,6 +21,10 @@ const PROJECT_ONLY_ENV = {
20
21
  };
21
22
  const SESSION_ID = "session-post-compact-directive";
22
23
 
24
+ // Runtime sentinel: the rules hook emits and re-greps this marker when
25
+ // deduplicating injected rules against the transcript (transcript-rule-filter.ts).
26
+ const INSTRUCTIONS_FROM = "Instructions from: ";
27
+
23
28
  afterEach(() => {
24
29
  for (const directory of tempDirectories.splice(0)) {
25
30
  rmSync(directory, { recursive: true, force: true });
@@ -48,10 +53,8 @@ describe("codex rules post-compaction read directive", () => {
48
53
  });
49
54
 
50
55
  // then
51
- expect(dynamicOutput).toContain("TypeScript rule");
56
+ expect(dynamicOutput).toContain("TS_RULE_SENTINEL");
52
57
  const context = readAdditionalContext(output);
53
- expect(context).toContain("MUST READ");
54
- expect(context).toContain("NO EXCUSES");
55
58
  expect(context).toContain("CONTEXT.md");
56
59
  expect(context).toContain(".omo/rules/typescript.md");
57
60
  expect(context).not.toContain("A".repeat(50));
@@ -125,9 +128,9 @@ describe("codex rules post-compaction read directive", () => {
125
128
 
126
129
  // then
127
130
  const context = readAdditionalContext(output);
128
- expect(context).toContain("You are Hephaestus");
129
- expect(context).not.toContain("[Truncated. Full:");
130
- expect(context).toContain("MUST READ");
131
+ const bundledRulePath = fileURLToPath(new URL("../bundled-rules/hephaestus/gpt-5.5.md", import.meta.url));
132
+ expect(context).toContain(`${INSTRUCTIONS_FROM}${bundledRulePath}`);
133
+ expect(context).toContain(parseRule(readFileSync(bundledRulePath, "utf8")).body.trim());
131
134
  expect(context).toContain("CONTEXT.md");
132
135
  });
133
136
  });
@@ -141,7 +144,7 @@ function makeProject(): { root: string; pluginData: string } {
141
144
  mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
142
145
  writeFileSync(
143
146
  path.join(root, ".omo", "rules", "typescript.md"),
144
- ["---", 'globs: "**/*.ts"', "---", "", `TypeScript rule\n${"B".repeat(2_000)}`].join("\n"),
147
+ ["---", 'globs: "**/*.ts"', "---", "", `TS_RULE_SENTINEL\n${"B".repeat(2_000)}`].join("\n"),
145
148
  );
146
149
  mkdirSync(path.join(root, "src"), { recursive: true });
147
150
  writeFileSync(path.join(root, "src", "app.ts"), "export const app = 1;\n");