Skip to content
This repository was archived by the owner on Jul 30, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
".clawpatch/",
".codex/",
"AGENTS.md",
"CLAUDE.md"
"CLAUDE.md",
"archive/**"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Info: Verified this ignore entry and the archive's internal consistency; both hold up.

The archive/** glob works. oxfmt's ignorePatterns here uses directory-suffix form elsewhere (.claude/, .codex/), so ** was worth confirming. Reproduced against oxfmt@0.57.0 in a scratch dir with a deliberately misformatted archive/config.json plus an identical copy outside archive/ — only the outside copy was flagged. And npx oxfmt@0.57.0 --check --config .oxfmtrc.json . passes on this branch (51 files), so the snapshots stay byte-verbatim as intended.

The archived renders match what sync-rules would have produced, which is a nice property for a record whose value is fidelity:

  • gemini-AGENTS.md and opencode-AGENTS.md are byte-identical (same md5, 377 lines) — consistent with archive/config.json declaring globalOverrides for only codex, copilot, and claude.
  • claude-CLAUDE.md (404), codex-AGENTS.md (388), and copilot-copilot-instructions.md (427) each contain the 377-line gemini render as an exact line-prefix, matching the documented "override rules are appended after the shared global rules" semantics in README.md:84.
  • The five filenames map 1:1 onto HARNESS_REGISTRY in src/core/harness-registry.ts:14-20.
  • archive/config.json satisfies the Config schema in src/config/config.ts: the three globalOverrides keys are all valid HarnessNames, global and every project's rules carry at least one positive glob, and projects is non-empty with no duplicate paths.

No action needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low: Excluding archive/** from oxfmt keeps the snapshots byte-verbatim, but there is a second mechanism that can interfere with that goal: .githooks/pre-commit runs git diff --cached --check with an exclude list (.agents/**, .claude/**, .clawpatch/**, .codex/**) that does not include archive/**. A future verbatim snapshot containing trailing whitespace would be rejected at commit time, pressuring someone to alter the bytes. I checked the current archive with grep -rlP ' +$' archive/ — it is clean, so nothing is broken today, and since the repo is being archived after this merge this is discretionary. If any follow-up snapshot lands before archival, consider adding :(exclude)archive/** to ignored_staged_paths.

]
}
42 changes: 41 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@ Before taking any action, read @README.md for project context.

Use `askpplx` for real-time web search via Perplexity. Verify external facts—documentation, API behavior, library versions, best practices—before acting on them. A lookup costs far less than debugging hallucinated code. Run `npx -y askpplx --help` if unsure of the available options.

# Rule: Safe Command Execution

## Store commands in arrays, not strings

When Bash expands a string variable, quotes inside become literal characters and whitespace triggers word splitting:

```bash
# BAD: quotes are literal, spaces split words
CMD="echo \"hello world\""
$CMD # outputs: "hello world" (with literal quotes)

# GOOD: array preserves argument boundaries
CMD=(echo "hello world")
"${CMD[@]}" # outputs: hello world
```

## Never interpolate variables into shell strings

Variables interpolated into shell strings — `sh -c`, `bash -c`, `eval`, `ssh host` — are reparsed by the shell. Characters like `$(...)`, backticks, or `;` in the value execute as code, a classic injection vector:

```bash
# BAD: if VAR contains $(malicious), it executes
sh -c "$VAR --write"

# GOOD: direct execution, no shell interpretation
"${CMD[@]}" --write

# GOOD: with xargs, execute the array directly
find . -name '*.js' -print0 | xargs -0 "${CMD[@]}" --write --
```

When you need shell features (pipes, redirects), use the `exec "$@"` pattern to pass arguments as positional parameters instead of interpolating them:

```bash
# GOOD: arguments passed as $@, not interpolated into the string
xargs -0 sh -c 'exec "$@"' _ "${CMD[@]}" --write --
```

The `_` occupies `$0` (the script name), leaving `$@` for the command and arguments. Any string works as the placeholder; `_` is conventional.

# Rule: Avoid Leaky Abstractions

Design interfaces around what callers need, not how the system works internally. An abstraction is leaky when using it correctly requires knowledge of underlying storage, infrastructure, or error behavior. Keep signatures consistent, return domain types instead of backend artifacts, and inject infrastructure dependencies through constructors rather than method parameters.
Expand Down Expand Up @@ -331,7 +371,7 @@ For Node.js 22.6–22.17, use `--experimental-strip-types`. Older versions requi

# Rule: Use `repoq` for Repository Queries

Use `repoq` for reading repository state instead of piping `git` or the forge CLI through `awk`/`jq`/`grep`. Each command handles edge cases (detached HEAD, unborn branches, missing auth) and returns validated JSON. Use raw `git` for commit/push/merge, and the repo's forge CLI for forge-side mutations (PRs, issues, releases) — `gh` for GitHub or `fgj` for Forgejo, per the detected provider. Run `npx -y repoq --help` if unsure of the available subcommands.
Use `repoq` for reading repository state instead of piping `git` or the forge CLI through `awk`/`jq`/`grep`. Each command handles edge cases (detached HEAD, unborn branches, missing auth) and returns validated JSON. Use raw `git` for commit/push/merge, and the repo's forge CLI for forge-side mutations (PRs, issues, releases) — `gh` for GitHub or `fgj` for Forgejo, per the detected provider. Run `npx -y repoq@latest --help` if unsure of the available subcommands; the explicit tag prevents `npx` from reusing a stale cached release.

# Rule: Discriminated Unions

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# sync-rules

> **⚠️ Retired (2026-07-29).** This tool is superseded by **j4k-align**
> (`j4k/align` on `code.j4k.dev`), which now generates every agent instruction
> file. Do not install or run `sync-rules` — a run would overwrite
> j4k-align-generated files with this repository's stale corpus rendering.
> See [`RETIREMENT.md`](./RETIREMENT.md) for the full retirement record.

A CLI tool to synchronize AI coding assistant rule files between a central repository and multiple projects.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
Expand Down
23 changes: 23 additions & 0 deletions RETIREMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# sync-rules is retired

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Alternative approach: Lead with the notice in README.md instead of relying on a separate file. A short banner at the top of the README (retired, superseded by j4k-align, don't run the CLI, link to RETIREMENT.md for the full record) is the conventional pattern for a retired project, and it's the only placement that reaches the GitHub landing page and the npm package page — README.md is in package.json's files, so the last published tarball keeps showing live install/usage instructions. RETIREMENT.md can stay as-is for the detailed record; the change is making the README the entry point to it rather than a sibling that gets scrolled past.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low: README.md (unchanged in this PR) still presents the tool as fully active — installation instructions, usage workflow, no mention of retirement. GitHub's archive banner will appear after archival, but RETIREMENT.md itself doesn't render on the repo landing page, so visitors arriving from the npm package page will see only the active-looking README. A one-line notice at the top of README linking to RETIREMENT.md would close that gap, and could double as the npm deprecate message.


Retired 2026-07-29. Agent instruction files (`AGENTS.md`, `CLAUDE.md`, the
workstation globals) are now generated by **j4k-align** (`j4k/align` on
`code.j4k.dev`) via `j4k-align agents --fix`, from the rule corpus in that
repository's `rules/` directory. The full migration record is
`docs/sync-rules-retirement-plan.md` in `j4k/align`.

- Deployed version read from the npm registry at cutover: **5.11.5**.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Info: Verified: the registry's dist-tags.latest for sync-rules is indeed 5.11.5, matching this recorded cutover version. (The repo's own package.json says 5.1.0, consistent with the release workflow not committing version bumps back.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Info: Verified the factual claims in this record against reality, all consistent:

  • npm view sync-rules version returns 5.11.5 (latest, last modified 2026-07-06), matching this line even though the committed package.json says 5.1.0 (expected with semantic-release).
  • archive/config.json parses cleanly against this repo's own Config schema (src/config/config.ts): 47 projects, all with positive globs, and the globalOverrides keys (codex, copilot, claude) are valid names per src/core/harness-registry.ts.
  • The config path claim matches the code: envPaths("sync-rules", { suffix: "" }) in src/config/constants.ts resolves to ~/Library/Preferences/sync-rules/config.json on macOS.
  • The five archived renders are internally consistent with the archived config: gemini-AGENTS.md and opencode-AGENTS.md are byte-identical (neither harness has overrides), and the claude/codex/copilot files each begin with that exact shared render followed by their override rules (verified with cmp).

- The final `config.json` (verbatim from
`~/Library/Preferences/sync-rules/config.json` at retirement) is archived at
[`archive/config.json`](./archive/config.json).
- The last sync-rules renders of the five workstation global files are archived
under [`archive/global-targets/`](./archive/global-targets/). These are the
bytes live on the workstation immediately before the j4k-align regeneration
(the 2026-07-27 corpus re-sync render); the Phase-0 baseline tar named by the
retirement plan was no longer present on the retiring machine, and its
per-file hashes remain recorded in j4k-align's `docs/pre-cutover-hashes.json`.

Phase 5 of the retirement plan deprecates the npm package on the registry
(never unpublishing it) and archives this repository once this record is on
`main`. Do not run the CLI — a run would overwrite j4k-align-generated files
with this repository's stale corpus rendering.
Loading
Loading