oh-my-openagent 4.7.2 → 4.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +14 -12
- package/package.json +12 -12
- package/packages/omo-codex/plugin/components/rules/README.md +4 -4
- package/packages/omo-codex/plugin/components/rules/skills/rules/SKILL.md +2 -4
- package/packages/omo-codex/plugin/components/rules/src/config.ts +1 -5
- package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +2 -11
- package/packages/omo-codex/plugin/components/rules/src/rules/constants.ts +1 -7
- package/packages/omo-codex/plugin/components/rules/src/rules/engine.ts +3 -12
- package/packages/omo-codex/plugin/components/rules/src/rules/finder-sources.ts +0 -5
- package/packages/omo-codex/plugin/components/rules/src/rules/sources.ts +13 -0
- package/packages/omo-codex/plugin/components/rules/src/rules/types.ts +2 -6
- package/packages/omo-codex/plugin/components/rules/test/agent-doc-sources.test.ts +119 -0
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts +4 -2
- package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts +4 -2
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +72 -1
- package/packages/omo-codex/plugin/components/rules/test/config.test.ts +22 -0
- package/packages/omo-codex/plugin/components/rules/test/dynamic-target-fingerprints.test.ts +71 -0
- package/packages/omo-codex/plugin/components/rules/test/engine.test.ts +52 -0
- package/packages/omo-codex/plugin/components/rules/test/finder.test.ts +2 -7
- package/packages/omo-codex/plugin/components/rules/test/formatter.test.ts +14 -14
- package/packages/omo-codex/plugin/components/rules/test/post-compact-test-fixture.ts +4 -2
- package/packages/omo-codex/plugin/components/rules/test/sources.test.ts +46 -0
- package/packages/omo-codex/plugin/components/ultrawork/README.md +2 -0
- package/packages/omo-codex/plugin/components/ultrawork/src/codex-hook.ts +62 -0
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +51 -0
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +67 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +51 -0
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +15 -5
- package/packages/omo-codex/plugin/skills/rules/SKILL.md +2 -4
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +87 -1
- package/packages/omo-codex/scripts/install/config.mjs +2 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +11 -6
package/dist/cli/index.js
CHANGED
|
@@ -61669,7 +61669,7 @@ var {
|
|
|
61669
61669
|
// package.json
|
|
61670
61670
|
var package_default = {
|
|
61671
61671
|
name: "oh-my-openagent",
|
|
61672
|
-
version: "4.7.
|
|
61672
|
+
version: "4.7.4",
|
|
61673
61673
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
61674
61674
|
main: "./dist/index.js",
|
|
61675
61675
|
types: "dist/index.d.ts",
|
|
@@ -61804,17 +61804,17 @@ var package_default = {
|
|
|
61804
61804
|
zod: "^4.4.3"
|
|
61805
61805
|
},
|
|
61806
61806
|
optionalDependencies: {
|
|
61807
|
-
"oh-my-openagent-darwin-arm64": "4.7.
|
|
61808
|
-
"oh-my-openagent-darwin-x64": "4.7.
|
|
61809
|
-
"oh-my-openagent-darwin-x64-baseline": "4.7.
|
|
61810
|
-
"oh-my-openagent-linux-arm64": "4.7.
|
|
61811
|
-
"oh-my-openagent-linux-arm64-musl": "4.7.
|
|
61812
|
-
"oh-my-openagent-linux-x64": "4.7.
|
|
61813
|
-
"oh-my-openagent-linux-x64-baseline": "4.7.
|
|
61814
|
-
"oh-my-openagent-linux-x64-musl": "4.7.
|
|
61815
|
-
"oh-my-openagent-linux-x64-musl-baseline": "4.7.
|
|
61816
|
-
"oh-my-openagent-windows-x64": "4.7.
|
|
61817
|
-
"oh-my-openagent-windows-x64-baseline": "4.7.
|
|
61807
|
+
"oh-my-openagent-darwin-arm64": "4.7.4",
|
|
61808
|
+
"oh-my-openagent-darwin-x64": "4.7.4",
|
|
61809
|
+
"oh-my-openagent-darwin-x64-baseline": "4.7.4",
|
|
61810
|
+
"oh-my-openagent-linux-arm64": "4.7.4",
|
|
61811
|
+
"oh-my-openagent-linux-arm64-musl": "4.7.4",
|
|
61812
|
+
"oh-my-openagent-linux-x64": "4.7.4",
|
|
61813
|
+
"oh-my-openagent-linux-x64-baseline": "4.7.4",
|
|
61814
|
+
"oh-my-openagent-linux-x64-musl": "4.7.4",
|
|
61815
|
+
"oh-my-openagent-linux-x64-musl-baseline": "4.7.4",
|
|
61816
|
+
"oh-my-openagent-windows-x64": "4.7.4",
|
|
61817
|
+
"oh-my-openagent-windows-x64-baseline": "4.7.4"
|
|
61818
61818
|
},
|
|
61819
61819
|
overrides: {
|
|
61820
61820
|
hono: "^4.12.18",
|
|
@@ -62940,6 +62940,8 @@ async function updateCodexConfig(input) {
|
|
|
62940
62940
|
config = removeStaleManagedAgentBlocks(config, new Set((input.agentConfigs ?? []).map((agentConfig) => agentConfig.name)));
|
|
62941
62941
|
config = ensureFeatureEnabled2(config, "plugins");
|
|
62942
62942
|
config = ensureFeatureEnabled2(config, "plugin_hooks");
|
|
62943
|
+
config = ensureFeatureEnabled2(config, "multi_agent");
|
|
62944
|
+
config = ensureFeatureEnabled2(config, "child_agents_md");
|
|
62943
62945
|
config = ensureCodexReasoningConfig(config, await readCodexModelCatalog(input.repoRoot));
|
|
62944
62946
|
config = ensureCodexMultiAgentV2Config(config);
|
|
62945
62947
|
if (input.autonomousPermissions === true)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-openagent",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.4",
|
|
4
4
|
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -135,17 +135,17 @@
|
|
|
135
135
|
"zod": "^4.4.3"
|
|
136
136
|
},
|
|
137
137
|
"optionalDependencies": {
|
|
138
|
-
"oh-my-openagent-darwin-arm64": "4.7.
|
|
139
|
-
"oh-my-openagent-darwin-x64": "4.7.
|
|
140
|
-
"oh-my-openagent-darwin-x64-baseline": "4.7.
|
|
141
|
-
"oh-my-openagent-linux-arm64": "4.7.
|
|
142
|
-
"oh-my-openagent-linux-arm64-musl": "4.7.
|
|
143
|
-
"oh-my-openagent-linux-x64": "4.7.
|
|
144
|
-
"oh-my-openagent-linux-x64-baseline": "4.7.
|
|
145
|
-
"oh-my-openagent-linux-x64-musl": "4.7.
|
|
146
|
-
"oh-my-openagent-linux-x64-musl-baseline": "4.7.
|
|
147
|
-
"oh-my-openagent-windows-x64": "4.7.
|
|
148
|
-
"oh-my-openagent-windows-x64-baseline": "4.7.
|
|
138
|
+
"oh-my-openagent-darwin-arm64": "4.7.4",
|
|
139
|
+
"oh-my-openagent-darwin-x64": "4.7.4",
|
|
140
|
+
"oh-my-openagent-darwin-x64-baseline": "4.7.4",
|
|
141
|
+
"oh-my-openagent-linux-arm64": "4.7.4",
|
|
142
|
+
"oh-my-openagent-linux-arm64-musl": "4.7.4",
|
|
143
|
+
"oh-my-openagent-linux-x64": "4.7.4",
|
|
144
|
+
"oh-my-openagent-linux-x64-baseline": "4.7.4",
|
|
145
|
+
"oh-my-openagent-linux-x64-musl": "4.7.4",
|
|
146
|
+
"oh-my-openagent-linux-x64-musl-baseline": "4.7.4",
|
|
147
|
+
"oh-my-openagent-windows-x64": "4.7.4",
|
|
148
|
+
"oh-my-openagent-windows-x64-baseline": "4.7.4"
|
|
149
149
|
},
|
|
150
150
|
"overrides": {
|
|
151
151
|
"hono": "^4.12.18",
|
|
@@ -17,8 +17,6 @@ The runtime has no npm production dependencies, so a clean Codex marketplace cop
|
|
|
17
17
|
|
|
18
18
|
Project-level sources:
|
|
19
19
|
|
|
20
|
-
- `AGENTS.md`
|
|
21
|
-
- `CLAUDE.md`
|
|
22
20
|
- `CONTEXT.md`
|
|
23
21
|
- `.omo/rules/**/*.md`
|
|
24
22
|
- `.claude/rules/**/*.md`
|
|
@@ -26,7 +24,7 @@ Project-level sources:
|
|
|
26
24
|
- `.github/instructions/**/*.md`
|
|
27
25
|
- `.github/copilot-instructions.md`
|
|
28
26
|
|
|
29
|
-
User-home sources are also supported by the ported engine when available.
|
|
27
|
+
User-home sources are also supported by the ported engine when available. `AGENTS.md` is not part of `auto` source selection because Codex already loads it as native project instructions, so re-injecting it through hooks duplicates context; opt into it explicitly with `CODEX_RULES_ENABLED_SOURCES` if you need hook-level migration behavior. Claude user-home sources (`~/.claude/rules`, `~/.claude/CLAUDE.md`) are also excluded from `auto` because they usually contain Claude Code runtime instructions rather than Codex rules; opt into them explicitly when you want that migration behavior.
|
|
30
28
|
|
|
31
29
|
Markdown rule files may use frontmatter such as:
|
|
32
30
|
|
|
@@ -58,6 +56,8 @@ It also enables:
|
|
|
58
56
|
[features]
|
|
59
57
|
plugins = true
|
|
60
58
|
plugin_hooks = true
|
|
59
|
+
multi_agent = true
|
|
60
|
+
child_agents_md = true
|
|
61
61
|
|
|
62
62
|
[plugins."omo@sisyphuslabs"]
|
|
63
63
|
enabled = true
|
|
@@ -73,7 +73,7 @@ Use `CODEX_RULES_*` environment variables:
|
|
|
73
73
|
| `CODEX_RULES_MODE` | `both`, `static`, `dynamic`, `off` | `both` |
|
|
74
74
|
| `CODEX_RULES_MAX_RULE_CHARS` | positive integer | `12000` |
|
|
75
75
|
| `CODEX_RULES_MAX_RESULT_CHARS` | positive integer | `40000` |
|
|
76
|
-
| `CODEX_RULES_ENABLED_SOURCES` | comma-separated source names | `auto` |
|
|
76
|
+
| `CODEX_RULES_ENABLED_SOURCES` | comma-separated source names or `auto` | `auto` (excludes `AGENTS.md`, `~/.claude/rules`, `~/.claude/CLAUDE.md`) |
|
|
77
77
|
|
|
78
78
|
For migration from `pi-rules`, equivalent `PI_RULES_*` variables are accepted as fallbacks.
|
|
79
79
|
|
|
@@ -14,10 +14,8 @@ Dynamic `PostToolUse` output is injected as additional context and is deduplicat
|
|
|
14
14
|
|
|
15
15
|
Supported project sources:
|
|
16
16
|
|
|
17
|
-
- `AGENTS.md`
|
|
18
|
-
- `CLAUDE.md`
|
|
19
17
|
- `CONTEXT.md`
|
|
20
|
-
- `.
|
|
18
|
+
- `.omo/rules/**/*.md`
|
|
21
19
|
- `.claude/rules/**/*.md`
|
|
22
20
|
- `.cursor/rules/**/*.md`
|
|
23
21
|
- `.github/instructions/**/*.md`
|
|
@@ -29,6 +27,6 @@ Supported environment knobs:
|
|
|
29
27
|
- `CODEX_RULES_MODE=both|static|dynamic|off`
|
|
30
28
|
- `CODEX_RULES_MAX_RULE_CHARS=<number>`
|
|
31
29
|
- `CODEX_RULES_MAX_RESULT_CHARS=<number>`
|
|
32
|
-
- `CODEX_RULES_ENABLED_SOURCES=
|
|
30
|
+
- `CODEX_RULES_ENABLED_SOURCES=CONTEXT.md,.omo/rules`
|
|
33
31
|
|
|
34
32
|
The legacy `PI_RULES_*` variables are accepted as fallbacks for users migrating from `pi-rules`.
|
|
@@ -77,7 +77,7 @@ function parseEnabledSources(value: string | undefined, disableBundledRules: boo
|
|
|
77
77
|
sources.push(source);
|
|
78
78
|
}
|
|
79
79
|
const enabledSources = disableBundledRules ? sources.filter((source) => source !== "plugin-bundled") : sources;
|
|
80
|
-
return enabledSources
|
|
80
|
+
return enabledSources;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function sourcesWithoutBundledRules(): RuleSource[] {
|
|
@@ -91,15 +91,11 @@ function toRuleSource(value: string): RuleSource | null {
|
|
|
91
91
|
case ".cursor/rules":
|
|
92
92
|
case ".github/instructions":
|
|
93
93
|
case ".github/copilot-instructions.md":
|
|
94
|
-
case "AGENTS.md":
|
|
95
|
-
case "CLAUDE.md":
|
|
96
94
|
case "CONTEXT.md":
|
|
97
95
|
case "plugin-bundled":
|
|
98
96
|
case "~/.omo/rules":
|
|
99
97
|
case "~/.opencode/rules":
|
|
100
98
|
case "~/.claude/rules":
|
|
101
|
-
case "~/.config/opencode/AGENTS.md":
|
|
102
|
-
case "~/.claude/CLAUDE.md":
|
|
103
99
|
return value;
|
|
104
100
|
default:
|
|
105
101
|
return null;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { isSameOrChildPath, toPosixPath, uniqueStrings } from "./path-utils.js";
|
|
4
|
-
import { SOURCE_PRIORITY } from "./rules/constants.js";
|
|
5
4
|
import { createRuleDiscoveryCache, findRuleCandidates } from "./rules/finder.js";
|
|
6
5
|
import { hashContent } from "./rules/matcher.js";
|
|
7
6
|
import { sortCandidates } from "./rules/ordering.js";
|
|
8
7
|
import { findProjectRoot } from "./rules/project-root.js";
|
|
8
|
+
import { disabledSourcesFromConfig } from "./rules/sources.js";
|
|
9
9
|
import type { PiRulesConfig, RuleCandidate } from "./rules/types.js";
|
|
10
10
|
|
|
11
11
|
export interface DynamicTargetFingerprint {
|
|
@@ -19,7 +19,7 @@ export function fingerprintDynamicTargets(
|
|
|
19
19
|
targetPaths: ReadonlyArray<string>,
|
|
20
20
|
config: PiRulesConfig,
|
|
21
21
|
): DynamicTargetFingerprint[] {
|
|
22
|
-
const disabledSources =
|
|
22
|
+
const disabledSources = disabledSourcesFromConfig(config);
|
|
23
23
|
const discoveryCache = createRuleDiscoveryCache();
|
|
24
24
|
const cwdProjectRoot = findProjectRoot(cwd);
|
|
25
25
|
const fingerprints: DynamicTargetFingerprint[] = [];
|
|
@@ -84,15 +84,6 @@ function fileFingerprint(filePath: string): string {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function disabledSourcesFor(config: PiRulesConfig): ReadonlySet<string> | undefined {
|
|
88
|
-
if (config.enabledSources === "auto") {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const enabledSources = new Set(config.enabledSources);
|
|
93
|
-
return new Set([...SOURCE_PRIORITY.keys()].filter((source) => !enabledSources.has(source)));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
87
|
function dynamicTargetCacheKey(targetPath: string): string {
|
|
97
88
|
return toPosixPath(resolve(targetPath));
|
|
98
89
|
}
|
|
@@ -30,8 +30,6 @@ export const PROJECT_RULE_SUBDIRS: ReadonlyArray<readonly [string, string]> = [
|
|
|
30
30
|
*/
|
|
31
31
|
export const PROJECT_SINGLE_FILES: readonly string[] = [
|
|
32
32
|
".github/copilot-instructions.md",
|
|
33
|
-
"AGENTS.md",
|
|
34
|
-
"CLAUDE.md",
|
|
35
33
|
"CONTEXT.md",
|
|
36
34
|
];
|
|
37
35
|
|
|
@@ -43,7 +41,7 @@ export const USER_HOME_RULE_SUBDIRS: readonly string[] = [".omo/rules", ".openco
|
|
|
43
41
|
/**
|
|
44
42
|
* User-home single-file rules. The first one to exist wins per "first-match" semantics.
|
|
45
43
|
*/
|
|
46
|
-
export const USER_HOME_SINGLE_FILES: readonly string[] = [
|
|
44
|
+
export const USER_HOME_SINGLE_FILES: readonly string[] = [];
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
47
|
* Bundled plugin rule directory relative to the rules component root.
|
|
@@ -64,14 +62,10 @@ export const SOURCE_PRIORITY: ReadonlyMap<RuleSource, number> = new Map([
|
|
|
64
62
|
[".cursor/rules", 2],
|
|
65
63
|
[".github/instructions", 3],
|
|
66
64
|
[".github/copilot-instructions.md", 4],
|
|
67
|
-
["AGENTS.md", 5],
|
|
68
|
-
["CLAUDE.md", 6],
|
|
69
65
|
["CONTEXT.md", 7],
|
|
70
66
|
["~/.omo/rules", 100],
|
|
71
67
|
["~/.opencode/rules", 101],
|
|
72
68
|
["~/.claude/rules", 102],
|
|
73
|
-
["~/.config/opencode/AGENTS.md", 103],
|
|
74
|
-
["~/.claude/CLAUDE.md", 104],
|
|
75
69
|
["plugin-bundled", 200],
|
|
76
70
|
]);
|
|
77
71
|
|
|
@@ -15,13 +15,13 @@ import {
|
|
|
15
15
|
DEFAULT_POST_COMPACT_MAX_RESULT_CHARS,
|
|
16
16
|
DEFAULT_POST_COMPACT_MAX_RULE_CHARS,
|
|
17
17
|
PROJECT_SINGLE_FILES,
|
|
18
|
-
SOURCE_PRIORITY,
|
|
19
18
|
} from "./constants.js";
|
|
20
19
|
import { createRuleDiscoveryCache, type RuleDiscoveryCache } from "./finder.js";
|
|
21
20
|
import { formatDynamicBlock, formatStaticBlock } from "./formatter.js";
|
|
22
21
|
import { hashContent, matchRule } from "./matcher.js";
|
|
23
22
|
import { sortCandidates } from "./ordering.js";
|
|
24
23
|
import { parseRule } from "./parser.js";
|
|
24
|
+
import { disabledSourcesFromConfig } from "./sources.js";
|
|
25
25
|
import type { LoadedRule, MatchReason, PiRulesConfig, RuleCandidate, RuleDiagnostic, SessionState } from "./types.js";
|
|
26
26
|
|
|
27
27
|
interface LoadedRuleContent {
|
|
@@ -97,7 +97,7 @@ export function createEngine(config: PiRulesConfig, deps: EngineDeps): Engine {
|
|
|
97
97
|
projectRoot,
|
|
98
98
|
targetFile: null,
|
|
99
99
|
};
|
|
100
|
-
const disabledSources =
|
|
100
|
+
const disabledSources = disabledSourcesFromConfig(config);
|
|
101
101
|
if (disabledSources !== undefined) {
|
|
102
102
|
findOptions.disabledSources = disabledSources;
|
|
103
103
|
}
|
|
@@ -121,7 +121,7 @@ export function createEngine(config: PiRulesConfig, deps: EngineDeps): Engine {
|
|
|
121
121
|
const seenRules = new Set<string>();
|
|
122
122
|
const loadedRuleContent = new Map<string, LoadedRuleContent | null>();
|
|
123
123
|
const projectMembership = new Map<string, boolean>();
|
|
124
|
-
const disabledSources =
|
|
124
|
+
const disabledSources = disabledSourcesFromConfig(config);
|
|
125
125
|
const discoveryCache = createRuleDiscoveryCache();
|
|
126
126
|
const candidateDiscoveryCache: CandidateDiscoveryCache = new Map();
|
|
127
127
|
const cwdProjectRoot = deps.findProjectRoot(cwd);
|
|
@@ -442,15 +442,6 @@ function staticMatchReason(rule: LoadedRule): MatchReason | null {
|
|
|
442
442
|
return null;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
function disabledSourcesFor(config: PiRulesConfig): ReadonlySet<string> | undefined {
|
|
446
|
-
if (config.enabledSources === "auto") {
|
|
447
|
-
return undefined;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
const enabledSources = new Set(config.enabledSources);
|
|
451
|
-
return new Set([...SOURCE_PRIORITY.keys()].filter((source) => !enabledSources.has(source)));
|
|
452
|
-
}
|
|
453
|
-
|
|
454
445
|
function isDedupedRootSingleFile(candidate: RuleCandidate, rootSingleFileSelected: boolean): boolean {
|
|
455
446
|
return rootSingleFileSelected && isRootSingleFile(candidate);
|
|
456
447
|
}
|
|
@@ -17,8 +17,6 @@ export function toProjectRuleSource(parentDirectory: string, subDirectory: strin
|
|
|
17
17
|
export function toProjectSingleFileSource(ruleFile: string): RuleSource {
|
|
18
18
|
switch (ruleFile) {
|
|
19
19
|
case ".github/copilot-instructions.md":
|
|
20
|
-
case "AGENTS.md":
|
|
21
|
-
case "CLAUDE.md":
|
|
22
20
|
case "CONTEXT.md":
|
|
23
21
|
return ruleFile;
|
|
24
22
|
default:
|
|
@@ -41,9 +39,6 @@ export function toUserHomeRuleSource(ruleSubdir: string): RuleSource {
|
|
|
41
39
|
export function toUserHomeSingleFileSource(ruleFile: string): RuleSource {
|
|
42
40
|
const source = `~/${ruleFile}`;
|
|
43
41
|
switch (source) {
|
|
44
|
-
case "~/.config/opencode/AGENTS.md":
|
|
45
|
-
case "~/.claude/CLAUDE.md":
|
|
46
|
-
return source;
|
|
47
42
|
default:
|
|
48
43
|
throw new UnsupportedRuleSourceError(`Unsupported user-home single-file source: ${source}`);
|
|
49
44
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SOURCE_PRIORITY } from "./constants.js";
|
|
2
|
+
import type { PiRulesConfig } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_AUTO_DISABLED_SOURCES: readonly string[] = ["AGENTS.md", "~/.claude/rules", "~/.claude/CLAUDE.md"];
|
|
5
|
+
|
|
6
|
+
export function disabledSourcesFromConfig(config: PiRulesConfig): ReadonlySet<string> | undefined {
|
|
7
|
+
if (config.enabledSources === "auto") {
|
|
8
|
+
return new Set(DEFAULT_AUTO_DISABLED_SOURCES);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const enabledSources = new Set(config.enabledSources);
|
|
12
|
+
return new Set([...SOURCE_PRIORITY.keys()].filter((source) => !enabledSources.has(source)));
|
|
13
|
+
}
|
|
@@ -51,7 +51,7 @@ export interface RuleCandidate {
|
|
|
51
51
|
distance: number;
|
|
52
52
|
isGlobal: boolean;
|
|
53
53
|
/**
|
|
54
|
-
* True when this candidate is a SINGLE-FILE rule like
|
|
54
|
+
* True when this candidate is a SINGLE-FILE rule like
|
|
55
55
|
* `.github/copilot-instructions.md` (frontmatter optional, applies always).
|
|
56
56
|
*/
|
|
57
57
|
isSingleFile: boolean;
|
|
@@ -81,15 +81,11 @@ export type RuleSource =
|
|
|
81
81
|
| ".cursor/rules"
|
|
82
82
|
| ".github/instructions"
|
|
83
83
|
| ".github/copilot-instructions.md"
|
|
84
|
-
| "AGENTS.md"
|
|
85
|
-
| "CLAUDE.md"
|
|
86
84
|
| "CONTEXT.md"
|
|
87
85
|
| "plugin-bundled"
|
|
88
86
|
| "~/.omo/rules"
|
|
89
87
|
| "~/.opencode/rules"
|
|
90
|
-
| "~/.claude/rules"
|
|
91
|
-
| "~/.config/opencode/AGENTS.md"
|
|
92
|
-
| "~/.claude/CLAUDE.md";
|
|
88
|
+
| "~/.claude/rules";
|
|
93
89
|
|
|
94
90
|
/**
|
|
95
91
|
* Why a candidate matched the target file. Surfaced in the injection block so
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type CodexPostToolUseInput,
|
|
8
|
+
type CodexSessionStartInput,
|
|
9
|
+
runPostToolUseHook,
|
|
10
|
+
runSessionStartHook,
|
|
11
|
+
} from "../src/codex-hook.js";
|
|
12
|
+
|
|
13
|
+
const REMOVED_AGENT_DOC_SOURCE_LISTS = ["AGENTS.md", "CLAUDE.md", "AGENTS.md,CLAUDE.md"] as const;
|
|
14
|
+
|
|
15
|
+
const tempDirectories: string[] = [];
|
|
16
|
+
|
|
17
|
+
type AgentDocProject = {
|
|
18
|
+
readonly pluginData: string;
|
|
19
|
+
readonly root: string;
|
|
20
|
+
readonly nestedSourceFile: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
for (const directory of tempDirectories.splice(0)) {
|
|
25
|
+
rmSync(directory, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function makeAgentDocProject(): AgentDocProject {
|
|
30
|
+
const root = mkdtempSync(path.join(tmpdir(), "codex-rules-agent-docs-"));
|
|
31
|
+
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-rules-agent-docs-data-"));
|
|
32
|
+
tempDirectories.push(root, pluginData);
|
|
33
|
+
|
|
34
|
+
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
|
|
35
|
+
writeFileSync(path.join(root, "AGENTS.md"), "Root AGENTS.md must remain Codex-native.");
|
|
36
|
+
writeFileSync(path.join(root, "CLAUDE.md"), "Root CLAUDE.md must stay outside rules hook context.");
|
|
37
|
+
writeFileSync(path.join(root, "CONTEXT.md"), "Context source must not leak through removed-only allowlists.");
|
|
38
|
+
|
|
39
|
+
mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
|
|
40
|
+
writeFileSync(
|
|
41
|
+
path.join(root, ".omo", "rules", "typescript.md"),
|
|
42
|
+
[
|
|
43
|
+
"---",
|
|
44
|
+
"description: TypeScript",
|
|
45
|
+
'globs: ["**/*.ts"]',
|
|
46
|
+
"---",
|
|
47
|
+
"",
|
|
48
|
+
"Dynamic .omo/rules context must not leak through removed-only allowlists.",
|
|
49
|
+
].join("\n"),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const nestedDirectory = path.join(root, "child", "src");
|
|
53
|
+
mkdirSync(nestedDirectory, { recursive: true });
|
|
54
|
+
writeFileSync(path.join(root, "child", "AGENTS.md"), "Child AGENTS.md must remain Codex-native.");
|
|
55
|
+
const nestedSourceFile = path.join(nestedDirectory, "app.ts");
|
|
56
|
+
writeFileSync(nestedSourceFile, "export const app = true;\n");
|
|
57
|
+
|
|
58
|
+
return { root, pluginData, nestedSourceFile };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sessionStartInput(root: string): CodexSessionStartInput {
|
|
62
|
+
return {
|
|
63
|
+
session_id: "session-1",
|
|
64
|
+
transcript_path: null,
|
|
65
|
+
cwd: root,
|
|
66
|
+
hook_event_name: "SessionStart",
|
|
67
|
+
model: "gpt-5.5",
|
|
68
|
+
permission_mode: "default",
|
|
69
|
+
source: "startup",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function postToolUseInput(root: string, filePath: string): CodexPostToolUseInput {
|
|
74
|
+
return {
|
|
75
|
+
session_id: "session-1",
|
|
76
|
+
turn_id: "turn-1",
|
|
77
|
+
transcript_path: null,
|
|
78
|
+
cwd: root,
|
|
79
|
+
hook_event_name: "PostToolUse",
|
|
80
|
+
model: "gpt-5.5",
|
|
81
|
+
permission_mode: "default",
|
|
82
|
+
tool_name: "mcp__filesystem__read_file",
|
|
83
|
+
tool_input: { path: filePath },
|
|
84
|
+
tool_response: { text: "file contents" },
|
|
85
|
+
tool_use_id: "call-1",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("agent doc sources", () => {
|
|
90
|
+
for (const sourceList of REMOVED_AGENT_DOC_SOURCE_LISTS) {
|
|
91
|
+
it(`#given ${sourceList} removed-only source allowlist #when SessionStart runs #then it emits no OMO rules context`, async () => {
|
|
92
|
+
// given
|
|
93
|
+
const { root, pluginData } = makeAgentDocProject();
|
|
94
|
+
|
|
95
|
+
// when
|
|
96
|
+
const output = await runSessionStartHook(sessionStartInput(root), {
|
|
97
|
+
pluginDataRoot: pluginData,
|
|
98
|
+
env: { CODEX_RULES_ENABLED_SOURCES: sourceList },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// then
|
|
102
|
+
expect(output).toBe("");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
it("#given nested AGENTS.md and removed-only source allowlist #when PostToolUse targets a child file #then it emits no dynamic OMO rules context", async () => {
|
|
107
|
+
// given
|
|
108
|
+
const { root, pluginData, nestedSourceFile } = makeAgentDocProject();
|
|
109
|
+
|
|
110
|
+
// when
|
|
111
|
+
const output = await runPostToolUseHook(postToolUseInput(root, nestedSourceFile), {
|
|
112
|
+
pluginDataRoot: pluginData,
|
|
113
|
+
env: { CODEX_RULES_ENABLED_SOURCES: "AGENTS.md,CLAUDE.md" },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// then
|
|
117
|
+
expect(output).toBe("");
|
|
118
|
+
});
|
|
119
|
+
});
|
package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-budget.test.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
const tempDirectories: string[] = [];
|
|
15
15
|
const PROJECT_RULES_ENV = {
|
|
16
|
-
CODEX_RULES_ENABLED_SOURCES: "
|
|
16
|
+
CODEX_RULES_ENABLED_SOURCES: "CONTEXT.md,.omo/rules",
|
|
17
17
|
CODEX_RULES_MAX_RESULT_CHARS: "50000",
|
|
18
18
|
CODEX_RULES_MAX_RULE_CHARS: "30000",
|
|
19
19
|
};
|
|
@@ -56,7 +56,9 @@ function makeOversizedProject(): { root: string; pluginData: string } {
|
|
|
56
56
|
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-rules-post-compact-budget-data-"));
|
|
57
57
|
tempDirectories.push(root, pluginData);
|
|
58
58
|
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
|
|
59
|
-
writeFileSync(path.join(root, "AGENTS.md"),
|
|
59
|
+
writeFileSync(path.join(root, "AGENTS.md"), "Project AGENTS.md should stay Codex-native.");
|
|
60
|
+
writeFileSync(path.join(root, "CLAUDE.md"), "Project CLAUDE.md should stay outside rules hook context.");
|
|
61
|
+
writeFileSync(path.join(root, "CONTEXT.md"), `Project rule\n${"A".repeat(30_000)}`);
|
|
60
62
|
mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
|
|
61
63
|
writeFileSync(
|
|
62
64
|
path.join(root, ".omo", "rules", "typescript.md"),
|
package/packages/omo-codex/plugin/components/rules/test/codex-hook-post-compact-dedup.test.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
|
|
16
16
|
const tempDirectories: string[] = [];
|
|
17
17
|
const PROJECT_ONLY_ENV = {
|
|
18
|
-
CODEX_RULES_ENABLED_SOURCES: "
|
|
18
|
+
CODEX_RULES_ENABLED_SOURCES: "CONTEXT.md,.omo/rules",
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
afterEach(() => {
|
|
@@ -145,7 +145,9 @@ function makeTempProject(): { root: string; pluginData: string } {
|
|
|
145
145
|
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-rules-compact-dedup-data-"));
|
|
146
146
|
tempDirectories.push(root, pluginData);
|
|
147
147
|
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
|
|
148
|
-
writeFileSync(path.join(root, "AGENTS.md"), "
|
|
148
|
+
writeFileSync(path.join(root, "AGENTS.md"), "Project AGENTS.md should stay Codex-native.");
|
|
149
|
+
writeFileSync(path.join(root, "CLAUDE.md"), "Project CLAUDE.md should stay outside rules hook context.");
|
|
150
|
+
writeFileSync(path.join(root, "CONTEXT.md"), "Always wear safety goggles when refactoring.");
|
|
149
151
|
mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
|
|
150
152
|
writeFileSync(
|
|
151
153
|
path.join(root, ".omo", "rules", "typescript.md"),
|
|
@@ -55,9 +55,17 @@ function runHookCli(input: string, subcommand = "post-tool-use", env: NodeJS.Pro
|
|
|
55
55
|
|
|
56
56
|
const tempDirectories: string[] = [];
|
|
57
57
|
const PROJECT_ONLY_ENV = {
|
|
58
|
+
CODEX_RULES_ENABLED_SOURCES: "CONTEXT.md,.omo/rules",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const AGENTS_AND_RULES_ENV = {
|
|
58
62
|
CODEX_RULES_ENABLED_SOURCES: "AGENTS.md,.omo/rules",
|
|
59
63
|
};
|
|
60
64
|
|
|
65
|
+
const CLAUDE_AND_RULES_ENV = {
|
|
66
|
+
CODEX_RULES_ENABLED_SOURCES: "CLAUDE.md,.omo/rules",
|
|
67
|
+
};
|
|
68
|
+
|
|
61
69
|
const RULES_ONLY_ENV = {
|
|
62
70
|
CODEX_RULES_ENABLED_SOURCES: ".omo/rules",
|
|
63
71
|
};
|
|
@@ -73,7 +81,9 @@ function makeTempProject(): { root: string; pluginData: string } {
|
|
|
73
81
|
const pluginData = mkdtempSync(path.join(tmpdir(), "codex-rules-data-"));
|
|
74
82
|
tempDirectories.push(root, pluginData);
|
|
75
83
|
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "fixture" }));
|
|
76
|
-
writeFileSync(path.join(root, "AGENTS.md"), "
|
|
84
|
+
writeFileSync(path.join(root, "AGENTS.md"), "Project AGENTS.md should stay Codex-native.");
|
|
85
|
+
writeFileSync(path.join(root, "CLAUDE.md"), "Project CLAUDE.md should stay outside rules hook context.");
|
|
86
|
+
writeFileSync(path.join(root, "CONTEXT.md"), "Always wear safety goggles when refactoring.");
|
|
77
87
|
mkdirSync(path.join(root, ".omo", "rules"), { recursive: true });
|
|
78
88
|
writeFileSync(
|
|
79
89
|
path.join(root, ".omo", "rules", "typescript.md"),
|
|
@@ -211,6 +221,50 @@ describe("codex rules hooks", () => {
|
|
|
211
221
|
expect(parsed.hookSpecificOutput?.additionalContext).toContain("Always wear safety goggles");
|
|
212
222
|
});
|
|
213
223
|
|
|
224
|
+
it("#given default auto sources #when SessionStart runs #then native Codex AGENTS.md is not duplicated", async () => {
|
|
225
|
+
// given
|
|
226
|
+
const { root, pluginData } = makeTempProject();
|
|
227
|
+
|
|
228
|
+
// when
|
|
229
|
+
const output = await runSessionStartHook(sessionStartInput(root), {
|
|
230
|
+
pluginDataRoot: pluginData,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// then
|
|
234
|
+
const parsed = parseHookOutput(output);
|
|
235
|
+
expect(parsed.hookSpecificOutput?.additionalContext).toContain("## Project Instructions");
|
|
236
|
+
expect(parsed.hookSpecificOutput?.additionalContext).not.toContain("Project AGENTS.md should stay Codex-native.");
|
|
237
|
+
expect(parsed.hookSpecificOutput?.additionalContext).not.toContain("Project CLAUDE.md should stay outside rules hook context.");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("#given project AGENTS.md #when SessionStart runs #then rules hook leaves AGENTS.md to Codex native handling", async () => {
|
|
241
|
+
// given
|
|
242
|
+
const { root, pluginData } = makeTempProject();
|
|
243
|
+
|
|
244
|
+
// when
|
|
245
|
+
const output = await runSessionStartHook(sessionStartInput(root), {
|
|
246
|
+
pluginDataRoot: pluginData,
|
|
247
|
+
env: AGENTS_AND_RULES_ENV,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// then
|
|
251
|
+
expect(output).toBe("");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("#given project CLAUDE.md #when SessionStart runs #then rules hook leaves CLAUDE.md out of context", async () => {
|
|
255
|
+
// given
|
|
256
|
+
const { root, pluginData } = makeTempProject();
|
|
257
|
+
|
|
258
|
+
// when
|
|
259
|
+
const output = await runSessionStartHook(sessionStartInput(root), {
|
|
260
|
+
pluginDataRoot: pluginData,
|
|
261
|
+
env: CLAUDE_AND_RULES_ENV,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// then
|
|
265
|
+
expect(output).toBe("");
|
|
266
|
+
});
|
|
267
|
+
|
|
214
268
|
it("#given static context already injected #when UserPromptSubmit runs #then it emits no duplicate context", async () => {
|
|
215
269
|
// given
|
|
216
270
|
const { root, pluginData } = makeTempProject();
|
|
@@ -351,6 +405,23 @@ describe("codex rules hooks", () => {
|
|
|
351
405
|
expect(readSessionCache(pluginData).dynamicTargetFingerprints).toEqual(cachedState.dynamicTargetFingerprints);
|
|
352
406
|
});
|
|
353
407
|
|
|
408
|
+
it("#given default auto sources #when excluded AGENTS.md changes #then PostToolUse fingerprint stays stable", async () => {
|
|
409
|
+
// given
|
|
410
|
+
const { root, pluginData } = makeTempProject();
|
|
411
|
+
const filePath = path.join(root, "src", "app.ts");
|
|
412
|
+
const input = postToolUseInput(root, filePath);
|
|
413
|
+
await runPostToolUseHook(input, { pluginDataRoot: pluginData });
|
|
414
|
+
const cachedState = readSessionCache(pluginData);
|
|
415
|
+
writeFileSync(path.join(root, "AGENTS.md"), "Native Codex instructions changed outside codex-rules auto.");
|
|
416
|
+
|
|
417
|
+
// when
|
|
418
|
+
const output = await runPostToolUseHook(input, { pluginDataRoot: pluginData });
|
|
419
|
+
|
|
420
|
+
// then
|
|
421
|
+
expect(output).toBe("");
|
|
422
|
+
expect(readSessionCache(pluginData).dynamicTargetFingerprints).toEqual(cachedState.dynamicTargetFingerprints);
|
|
423
|
+
});
|
|
424
|
+
|
|
354
425
|
it("#given dynamic context remains in transcript but cache is missing #when PostToolUse repeats #then it emits no duplicate context", async () => {
|
|
355
426
|
// given
|
|
356
427
|
const { root, pluginData } = makeTempProject();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { configFromEnvironment } from "../src/config.js";
|
|
4
|
+
|
|
5
|
+
const REMOVED_AGENT_DOC_SOURCE_LISTS = ["AGENTS.md", "CLAUDE.md", "AGENTS.md,CLAUDE.md"] as const;
|
|
6
|
+
|
|
7
|
+
describe("rules config", () => {
|
|
8
|
+
for (const sourceList of REMOVED_AGENT_DOC_SOURCE_LISTS) {
|
|
9
|
+
it(`#given removed agent-doc source ${sourceList} #when parsing enabled sources #then preserves the explicit empty allowlist`, () => {
|
|
10
|
+
// given
|
|
11
|
+
const env = {
|
|
12
|
+
CODEX_RULES_ENABLED_SOURCES: sourceList,
|
|
13
|
+
} satisfies NodeJS.ProcessEnv;
|
|
14
|
+
|
|
15
|
+
// when
|
|
16
|
+
const config = configFromEnvironment(env);
|
|
17
|
+
|
|
18
|
+
// then
|
|
19
|
+
expect(config.enabledSources).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|