Merge pull request #4200 from code-yeongyu/fix/rules-core-restore-sisyphus-with-deprecation-warning
fix(rules-core): restore .sisyphus/rules discovery with deprecation warning (planned removal v4.3.0)
This commit is contained in:
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [4.2.3] - Unreleased
|
||||
|
||||
### Reverted Breaking Changes
|
||||
|
||||
- Restored `.sisyphus/rules` and `~/.sisyphus/rules` rule-source discovery that was silently removed in v4.2.2..HEAD. They now load with LOWEST priority among project rule sources and emit a deprecation warning. **Planned removal in v4.3.0**: migrate to `.omo/rules` and `~/.omo/rules`.
|
||||
|
||||
## [4.2.1] - Unreleased
|
||||
|
||||
### Fixed
|
||||
@@ -49,5 +55,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **Delegated child-session early-failure fallback (BLOCKER-4)**: PR #3825's `fac90d69f` was reverted by PR #4044 because its own regression test failed on clean root `bun test`. The delegate-task fallback bug for empty session history remains unaddressed in v4.2.0. Reland targets v4.2.1 once the regression test is stabilized against post-#4032 schema and the new gate semantics. See `docs/reference/known-issues.md` for details and workaround.
|
||||
- **First-prompt watchdog supersession history (L16)**: PR #3952 was superseded by PR #4051 (rebased over #4007/factory refactor with `internallyAbortedSessions` threading). The supersession represents conflict resolution, not a feature pivot. The final watchdog logic shipped via #4051 + `a130fa70d` covers subagent first-prompt silence past 90 seconds with cleanup via session.deleted.
|
||||
|
||||
[4.2.3]: https://github.com/code-yeongyu/oh-my-openagent/compare/v4.2.2...HEAD
|
||||
[4.2.1]: https://github.com/code-yeongyu/oh-my-openagent/compare/v4.2.0...HEAD
|
||||
[4.2.0]: https://github.com/code-yeongyu/oh-my-openagent/compare/v4.1.2...v4.2.0
|
||||
|
||||
@@ -7,10 +7,11 @@ export const PROJECT_RULE_SUBDIRS = [
|
||||
[".claude", "rules"],
|
||||
[".cursor", "rules"],
|
||||
[".github", "instructions"],
|
||||
[".sisyphus", "rules"],
|
||||
] as const;
|
||||
|
||||
export const PROJECT_RULE_FILES = [".github/copilot-instructions.md"] as const;
|
||||
export const OPENCODE_USER_RULE_DIRS = [".omo/rules", ".opencode/rules"] as const;
|
||||
export const OPENCODE_USER_RULE_DIRS = [".omo/rules", ".opencode/rules", ".sisyphus/rules"] as const;
|
||||
export const USER_RULE_DIR = ".claude/rules";
|
||||
export const RULE_EXTENSIONS = [".md", ".mdc"] as const;
|
||||
export const GITHUB_INSTRUCTIONS_PATTERN = /\.instructions\.md$/;
|
||||
@@ -24,7 +25,9 @@ export const SOURCE_PRIORITY: ReadonlyMap<RuleSource, number> = new Map([
|
||||
[".cursor/rules", 2],
|
||||
[".github/instructions", 3],
|
||||
[".github/copilot-instructions.md", 4],
|
||||
[".sisyphus/rules", 5],
|
||||
["~/.omo/rules", 100],
|
||||
["~/.opencode/rules", 101],
|
||||
["~/.claude/rules", 102],
|
||||
["~/.sisyphus/rules", 103],
|
||||
]);
|
||||
|
||||
@@ -5,6 +5,12 @@ import { GLOBAL_DISTANCE, OPENCODE_USER_RULE_DIRS, PROJECT_RULE_FILES, PROJECT_R
|
||||
import { sortCandidates } from "./ordering";
|
||||
import { findRuleFilesRecursive, safeRealpathSync } from "./scanner";
|
||||
import type { DirectoryScanEntry, FindRuleFilesOptions, RuleFileCandidate, RuleScanCache, RuleSource } from "./types";
|
||||
import { log } from "../../../src/shared/logger";
|
||||
|
||||
const SISYPHUS_DEPRECATION_MESSAGE = "[rules] .sisyphus/rules is deprecated and will be removed in v4.3.0; migrate to .omo/rules";
|
||||
const SISYPHUS_LEGACY_RULE_SOURCES: ReadonlySet<RuleSource> = new Set([".sisyphus/rules", "~/.sisyphus/rules"]);
|
||||
const warnedSisyphusRuleDirectories = new Set<string>();
|
||||
let logSisyphusRuleDeprecation: typeof log = log;
|
||||
|
||||
export function findRuleFiles(
|
||||
projectRoot: string | null,
|
||||
@@ -62,6 +68,7 @@ function addProjectRuleCandidates(
|
||||
for (const entry of scanDirectoryWithCache(ruleDir, cache)) {
|
||||
if (seenRealPaths.has(entry.realPath)) continue;
|
||||
seenRealPaths.add(entry.realPath);
|
||||
warnSisyphusRuleDeprecation(source, entry.path);
|
||||
candidates.push({
|
||||
path: entry.path,
|
||||
realPath: entry.realPath,
|
||||
@@ -115,6 +122,7 @@ function addUserRuleCandidates(
|
||||
for (const entry of scanDirectoryWithCache(userRuleDir, cache)) {
|
||||
if (seenRealPaths.has(entry.realPath)) continue;
|
||||
seenRealPaths.add(entry.realPath);
|
||||
warnSisyphusRuleDeprecation(source, entry.path);
|
||||
candidates.push({
|
||||
path: entry.path,
|
||||
realPath: entry.realPath,
|
||||
@@ -136,6 +144,26 @@ function scanDirectoryWithCache(dir: string, cache: RuleScanCache | undefined):
|
||||
return entries;
|
||||
}
|
||||
|
||||
function warnSisyphusRuleDeprecation(source: RuleSource, path: string): void {
|
||||
if (!SISYPHUS_LEGACY_RULE_SOURCES.has(source)) return;
|
||||
const warningKey = dirname(path);
|
||||
if (warnedSisyphusRuleDirectories.has(warningKey)) return;
|
||||
warnedSisyphusRuleDirectories.add(warningKey);
|
||||
logSisyphusRuleDeprecation(SISYPHUS_DEPRECATION_MESSAGE, {
|
||||
event: "rules-sisyphus-deprecated",
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
export function _setSisyphusRuleDeprecationLoggerForTesting(logger: typeof log): void {
|
||||
logSisyphusRuleDeprecation = logger;
|
||||
}
|
||||
|
||||
export function _resetSisyphusRuleDeprecationWarningStateForTesting(): void {
|
||||
warnedSisyphusRuleDirectories.clear();
|
||||
logSisyphusRuleDeprecation = log;
|
||||
}
|
||||
|
||||
function validFileRealPath(filePath: string): string | null {
|
||||
if (!existsSync(filePath)) return null;
|
||||
try {
|
||||
|
||||
@@ -13,9 +13,12 @@ import {
|
||||
parseRuleFrontmatter,
|
||||
shouldApplyRule,
|
||||
} from "./index";
|
||||
import { _resetSisyphusRuleDeprecationWarningStateForTesting, _setSisyphusRuleDeprecationLoggerForTesting } from "./finder";
|
||||
|
||||
let testRoot: string | null = null;
|
||||
|
||||
const SISYPHUS_DEPRECATION_MESSAGE = "[rules] .sisyphus/rules is deprecated and will be removed in v4.3.0; migrate to .omo/rules";
|
||||
|
||||
function createTestRoot(name: string): string {
|
||||
testRoot = join(tmpdir(), `${name}-${Date.now()}-${Math.random()}`);
|
||||
mkdirSync(testRoot, { recursive: true });
|
||||
@@ -23,6 +26,7 @@ function createTestRoot(name: string): string {
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
_resetSisyphusRuleDeprecationWarningStateForTesting();
|
||||
if (testRoot) {
|
||||
rmSync(testRoot, { recursive: true, force: true });
|
||||
testRoot = null;
|
||||
@@ -58,8 +62,55 @@ describe("rules-core", () => {
|
||||
".claude/rules/claude.md",
|
||||
".cursor/rules/cursor.md",
|
||||
".github/instructions/github.instructions.md",
|
||||
".sisyphus/rules/sisyphus.md",
|
||||
]);
|
||||
expect(found.map((rule) => rule.relativePath)).not.toContain(".sisyphus/rules/sisyphus.md");
|
||||
});
|
||||
|
||||
it("#given a workspace with .sisyphus/rules/*.md #when findRuleFiles is called #then those files are discovered with lowest priority among project sources", () => {
|
||||
// given
|
||||
const root = createTestRoot("rules-core-sisyphus-restored");
|
||||
mkdirSync(join(root, ".git"));
|
||||
mkdirSync(join(root, ".omo", "rules"), { recursive: true });
|
||||
mkdirSync(join(root, ".sisyphus", "rules"), { recursive: true });
|
||||
mkdirSync(join(root, "src"), { recursive: true });
|
||||
writeFileSync(join(root, ".omo", "rules", "shared.md"), "omo");
|
||||
writeFileSync(join(root, ".sisyphus", "rules", "shared.md"), "legacy");
|
||||
writeFileSync(join(root, ".sisyphus", "rules", "legacy.md"), "legacy");
|
||||
|
||||
// when
|
||||
const found = findRuleFiles(root, root, join(root, "src", "index.ts"));
|
||||
const relativePaths = found.map((rule) => rule.relativePath);
|
||||
const omoSharedIndex = relativePaths.indexOf(".omo/rules/shared.md");
|
||||
const sisyphusSharedIndex = relativePaths.indexOf(".sisyphus/rules/shared.md");
|
||||
|
||||
// then
|
||||
expect(relativePaths).toContain(".sisyphus/rules/legacy.md");
|
||||
expect(omoSharedIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(sisyphusSharedIndex).toBeGreaterThan(omoSharedIndex);
|
||||
});
|
||||
|
||||
it("#given .sisyphus/rules is discovered #when the finder runs #then a deprecation warning is logged exactly once", () => {
|
||||
// given
|
||||
const root = createTestRoot("rules-core-sisyphus-warning");
|
||||
const legacyRulePath = join(root, ".sisyphus", "rules", "legacy.md");
|
||||
mkdirSync(join(root, ".git"));
|
||||
mkdirSync(join(root, ".sisyphus", "rules"), { recursive: true });
|
||||
mkdirSync(join(root, "src"), { recursive: true });
|
||||
writeFileSync(legacyRulePath, "legacy");
|
||||
const warnings: Array<{ readonly message: string; readonly data: unknown }> = [];
|
||||
_setSisyphusRuleDeprecationLoggerForTesting((message, data) => {
|
||||
warnings.push({ message, data });
|
||||
});
|
||||
|
||||
// when
|
||||
findRuleFiles(root, root, join(root, "src", "index.ts"));
|
||||
findRuleFiles(root, root, join(root, "src", "index.ts"));
|
||||
const deprecationWarnings = warnings.filter(
|
||||
({ message, data }) => message === SISYPHUS_DEPRECATION_MESSAGE && isSisyphusDeprecationData(data, legacyRulePath),
|
||||
);
|
||||
|
||||
// then
|
||||
expect(deprecationWarnings).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("#given a workspace directory has no project marker (no .git, no package.json, etc.) AND contains .omo/rules/ #when findRuleFiles is called #then the .omo/rules/ files are still discovered", () => {
|
||||
@@ -136,7 +187,7 @@ describe("rules-core", () => {
|
||||
|
||||
// then
|
||||
expect(first).toEqual(second);
|
||||
expect(cache.stats()).toEqual({ candidateEntries: 1, directoryEntries: 9 });
|
||||
expect(cache.stats()).toEqual({ candidateEntries: 1, directoryEntries: 11 });
|
||||
});
|
||||
|
||||
it("#given nested project markers #when finding project root #then memoizes ancestor lookups", () => {
|
||||
@@ -154,3 +205,9 @@ describe("rules-core", () => {
|
||||
expect(second).toBe(root);
|
||||
});
|
||||
});
|
||||
|
||||
function isSisyphusDeprecationData(data: unknown, path: string): boolean {
|
||||
if (typeof data !== "object" || data === null) return false;
|
||||
if (!("event" in data) || !("path" in data)) return false;
|
||||
return data.event === "rules-sisyphus-deprecated" && data.path === path;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@ export type RuleSource =
|
||||
| ".cursor/rules"
|
||||
| ".github/instructions"
|
||||
| ".github/copilot-instructions.md"
|
||||
| ".sisyphus/rules"
|
||||
| "~/.omo/rules"
|
||||
| "~/.opencode/rules"
|
||||
| "~/.claude/rules";
|
||||
| "~/.claude/rules"
|
||||
| "~/.sisyphus/rules";
|
||||
|
||||
export interface MatchResult {
|
||||
readonly applies: boolean;
|
||||
|
||||
@@ -87,10 +87,10 @@ async function createProcessor(projectRoot: string): Promise<{
|
||||
trackedShouldApplyRuleCount += 1;
|
||||
return { applies: true, reason: "matched" };
|
||||
},
|
||||
isDuplicateByRealPath: (realPath: string, cache: Set<string>) =>
|
||||
isDuplicateByRealPath: (realPath: string, cache: ReadonlySet<string>) =>
|
||||
cache.has(realPath),
|
||||
createContentHash: (content: string) => `hash:${content}`,
|
||||
isDuplicateByContentHash: (hash: string, cache: Set<string>) =>
|
||||
isDuplicateByContentHash: (hash: string, cache: ReadonlySet<string>) =>
|
||||
cache.has(hash),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user