agency-rules 0.4.0 → 0.5.0
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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/agency-rules.mjs +17 -6
- package/package.json +3 -2
- package/rules/base/coding-standards.md +12 -0
- package/rules/base/commit-convention.md +53 -0
- package/rules/stacks/pyhton.md +12 -0
- package/rules/stacks/typescript.md +25 -0
- package/scripts/convert.mjs +17 -9
- package/scripts/install.mjs +8 -2
- package/scripts/list.mjs +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0](https://github.com/deuriib/agency-rules/compare/v0.4.1...v0.5.0) (2026-10-31)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **cli:** add 'list' command to display available rules ([15a7302](https://github.com/deuriib/agency-rules/commit/15a73025cd8ae1f90ab4a8602ac68817306aa128))
|
|
14
|
+
|
|
15
|
+
## [0.4.1](https://github.com/deuriib/agency-rules/compare/v0.4.0...v0.4.1) (2026-10-31)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **pkg:** include rules directory in npm bundle and resolve package paths ([3877a97](https://github.com/deuriib/agency-rules/commit/3877a97e22ec5b79c183c47f3ed7b026171684e5))
|
|
21
|
+
|
|
8
22
|
## [0.4.0](https://github.com/deuriib/agency-rules/compare/v0.3.2...v0.4.0) (2026-10-31)
|
|
9
23
|
|
|
10
24
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`agency-rules` compiles a single set of generic Markdown rules into the
|
|
6
6
|
native formats expected by **Google Antigravity**, **pi-agent**,
|
|
7
|
-
**oh-my-pi (omp)**, and **OpenCode** (via the
|
|
7
|
+
**oh-my-pi (omp)**, and **OpenCode** (via the [opencode-rules]("https://github.com/frap129/opencode-rules") plugin).
|
|
8
8
|
|
|
9
9
|
The whole pipeline — toolchain, environment, and tasks — is managed by
|
|
10
10
|
[mise](https://mise.jdx.dev), so every collaborator and CI runner uses the
|
|
@@ -21,7 +21,7 @@ Every coding agent has its own rule format:
|
|
|
21
21
|
| Google Antigravity | YAML frontmatter with `trigger` | `.agents/rules/*.md` |
|
|
22
22
|
| pi-agent | Per-rule directory with `RULES.md` | `.pi/rules/<slug>/RULES.md` |
|
|
23
23
|
| oh-my-pi (omp) | Markdown with frontmatter | `.omp/rules/*.{md,mdc}` |
|
|
24
|
-
| OpenCode | Markdown via
|
|
24
|
+
| OpenCode | Markdown via [opencode-rules]("https://github.com/frap129/opencode-rules") plugin | `.opencode/rules/*.md` |
|
|
25
25
|
|
|
26
26
|
Writing the same rule four times is a maintenance nightmare. `agency-rules`
|
|
27
27
|
lets you write it **once** in a neutral format and compile it to all four
|
package/bin/agency-rules.mjs
CHANGED
|
@@ -29,17 +29,18 @@ Write rules once. Run them in every harness.
|
|
|
29
29
|
$ agency-rules <command> [options]
|
|
30
30
|
|
|
31
31
|
\x1b[33mCommands:\x1b[0m
|
|
32
|
-
\x1b[
|
|
33
|
-
\x1b[
|
|
34
|
-
\x1b[
|
|
32
|
+
\x1b[32mlist\x1b[0m List all available rules
|
|
33
|
+
\x1b[32mconvert\x1b[0m Compile rules/ into each harness's native format
|
|
34
|
+
\x1b[32minstall\x1b[0m Install compiled artifacts into detected harnesses
|
|
35
|
+
\x1b[32mvalidate\x1b[0m Validate each rule's frontmatter against the schema
|
|
35
36
|
|
|
36
37
|
\x1b[33mGlobal Options:\x1b[0m
|
|
37
38
|
-h, --help Show this help message
|
|
38
39
|
-v, --version Show version number
|
|
39
40
|
|
|
40
41
|
\x1b[33mExamples:\x1b[0m
|
|
42
|
+
$ agency-rules list
|
|
41
43
|
$ agency-rules validate
|
|
42
|
-
$ agency-rules convert
|
|
43
44
|
$ agency-rules convert --dry-run
|
|
44
45
|
$ agency-rules install --scope user --harness opencode
|
|
45
46
|
`);
|
|
@@ -47,6 +48,17 @@ Write rules once. Run them in every harness.
|
|
|
47
48
|
|
|
48
49
|
const showCommandHelp = (cmd) => {
|
|
49
50
|
const helps = {
|
|
51
|
+
list: `
|
|
52
|
+
\x1b[36magency-rules list\x1b[0m
|
|
53
|
+
List all available rules and their descriptions.
|
|
54
|
+
|
|
55
|
+
\x1b[33mOptions:\x1b[0m
|
|
56
|
+
--json Output the list in JSON format (useful for scripting)
|
|
57
|
+
|
|
58
|
+
\x1b[33mExamples:\x1b[0m
|
|
59
|
+
$ agency-rules list
|
|
60
|
+
$ agency-rules list --json | jq '.'
|
|
61
|
+
`,
|
|
50
62
|
convert: `
|
|
51
63
|
\x1b[36magency-rules convert\x1b[0m
|
|
52
64
|
Compile rules/ into each harness's native format.
|
|
@@ -101,7 +113,7 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
|
|
|
101
113
|
process.exit(0);
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
if (!['convert', 'install', 'validate'].includes(command)) {
|
|
116
|
+
if (!['list', 'convert', 'install', 'validate'].includes(command)) {
|
|
105
117
|
console.error(`\x1b[31mError:\x1b[0m Unknown command '${command}'`);
|
|
106
118
|
showHelp();
|
|
107
119
|
process.exit(1);
|
|
@@ -114,7 +126,6 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
// ── Execute ───────────────────────────────────────────────────────────
|
|
117
|
-
// Remove the command from argv so the downstream script's parseArgs works cleanly
|
|
118
129
|
process.argv.splice(2, 1);
|
|
119
130
|
|
|
120
131
|
const scriptPath = resolve(import.meta.dirname, '..', 'scripts', `${command}.mjs`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agency-rules",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Write rules once. Run them in every harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
"node": ">=20.12"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
|
+
"bin/",
|
|
32
33
|
"scripts/",
|
|
33
34
|
"tools.json",
|
|
34
|
-
"rules/
|
|
35
|
+
"rules/",
|
|
35
36
|
"docs/",
|
|
36
37
|
"README.md",
|
|
37
38
|
"LICENSE",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coding-standards
|
|
3
|
+
description: Baseline rules applied to every edit regardless of file type.
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
tags: [base]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Coding Standards
|
|
9
|
+
|
|
10
|
+
- Keep functions under 40 lines.
|
|
11
|
+
- Prefer named exports over default exports.
|
|
12
|
+
- Never commit `console.log` or debug prints.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit-conventions
|
|
3
|
+
description: Enforce Conventional Commits format for all git commit messages.
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
tags: [git, commits, conventional-commits]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Git Commit Conventions
|
|
9
|
+
|
|
10
|
+
All commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification.
|
|
11
|
+
This is strictly required because automated release tooling (like `release-please`) uses these commits to calculate semantic versioning bumps and generate changelogs.
|
|
12
|
+
|
|
13
|
+
## Format
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
<type>(<optional scope>): <subject>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **type**: Must be one of the following:
|
|
20
|
+
- `feat`: A new feature (triggers a MINOR version bump).
|
|
21
|
+
- `fix`: A bug fix (triggers a PATCH version bump).
|
|
22
|
+
- `docs`: Documentation only changes.
|
|
23
|
+
- `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc).
|
|
24
|
+
- `refactor`: A code change that neither fixes a bug nor adds a feature.
|
|
25
|
+
- `perf`: A code change that improves performance.
|
|
26
|
+
- `test`: Adding missing tests or correcting existing tests.
|
|
27
|
+
- `build`: Changes that affect the build system or external dependencies.
|
|
28
|
+
- `ci`: Changes to CI configuration files and scripts.
|
|
29
|
+
- `chore`: Other changes that don't modify src or test files.
|
|
30
|
+
- **scope** (optional): A noun describing a section of the codebase (e.g., `rules`, `convert`, `harness`, `docs`).
|
|
31
|
+
- **subject**: A short description in the imperative mood (e.g., "add", not "added").
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
- Use the **imperative, present tense** (e.g., "fix bug" not "fixed bug").
|
|
36
|
+
- Do **not** capitalize the first letter of the subject.
|
|
37
|
+
- Do **not** end the subject line with a period.
|
|
38
|
+
- Keep the subject line under **72 characters**.
|
|
39
|
+
- Separate the subject from the body with a blank line.
|
|
40
|
+
- Use the body to explain *what* and *why* (not *how*).
|
|
41
|
+
- Use footers for referencing issues (e.g., `Closes #123`) or declaring breaking changes (`BREAKING CHANGE: <description>`).
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
feat(rules): add python async conventions
|
|
47
|
+
fix(antigravity): join globs with comma instead of space
|
|
48
|
+
docs(spec): clarify trigger precedence for model_decision
|
|
49
|
+
chore(deps): bump biome to 2.5.1
|
|
50
|
+
feat(harness): support Cursor rules format
|
|
51
|
+
|
|
52
|
+
BREAKING CHANGE: The `trigger` field is now required for all Antigravity rules.
|
|
53
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript
|
|
3
|
+
description: Mandatory rules for editing TypeScript and TSX files.
|
|
4
|
+
globs:
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
- "**/*.tsx"
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
trigger: glob # preferencia para Antigravity; otros harnesses lo ignoran si no aplica
|
|
9
|
+
tags: [language, typescript]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# TypeScript Rules
|
|
13
|
+
|
|
14
|
+
- No `any` unless absolutely necessary.
|
|
15
|
+
- Use top-level imports only.
|
|
16
|
+
- Prefer `interface` over `type` for object shapes.
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// ✅ good
|
|
22
|
+
interface User { id: string; name: string; }
|
|
23
|
+
|
|
24
|
+
// ❌ avoid
|
|
25
|
+
type User = { id: string; name: string; };
|
package/scripts/convert.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import { readRules } from './lib/frontmatter.mjs';
|
|
5
5
|
import { renderers } from './lib/renderers/index.mjs';
|
|
@@ -14,17 +14,25 @@ const { values: args } = parseArgs({
|
|
|
14
14
|
const dryRun = args['dry-run'];
|
|
15
15
|
const prefix = dryRun ? ' [dry-run]' : ' ';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Resolve paths: Use local if exists, otherwise fallback to the package's bundled files
|
|
18
|
+
const PACKAGE_ROOT = resolve(import.meta.dirname, '..');
|
|
19
|
+
const localRules = resolve(process.cwd(), 'rules');
|
|
20
|
+
const localTools = resolve(process.cwd(), 'tools.json');
|
|
21
|
+
|
|
22
|
+
const RULES_DIR =
|
|
23
|
+
process.env.RULES_DIR ||
|
|
24
|
+
((await import('node:fs').then((fs) => fs.existsSync(localRules)))
|
|
25
|
+
? localRules
|
|
26
|
+
: resolve(PACKAGE_ROOT, 'rules'));
|
|
27
|
+
const TOOLS_PATH = (await import('node:fs').then((fs) => fs.existsSync(localTools)))
|
|
28
|
+
? localTools
|
|
29
|
+
: resolve(PACKAGE_ROOT, 'tools.json');
|
|
30
|
+
|
|
18
31
|
const INTEGRATIONS_DIR = process.env.INTEGRATIONS_DIR || 'integrations';
|
|
19
32
|
|
|
20
|
-
const tools = JSON.parse(await readFile(
|
|
33
|
+
const tools = JSON.parse(await readFile(TOOLS_PATH, 'utf8'));
|
|
21
34
|
const rules = await readRules(RULES_DIR);
|
|
22
35
|
|
|
23
|
-
if (rules.length === 0) {
|
|
24
|
-
console.error(`❌ No rules found in ${RULES_DIR}`);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
36
|
console.log(
|
|
29
37
|
`📚 Found ${rules.length} rule(s) in ${RULES_DIR}${dryRun ? ' (dry-run — nothing will be written)' : ''}\n`,
|
|
30
38
|
);
|
package/scripts/install.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
4
|
import { stdin as input, stdout as output } from 'node:process';
|
|
4
5
|
import { createInterface } from 'node:readline/promises';
|
|
5
6
|
import { parseArgs } from 'node:util';
|
|
@@ -19,7 +20,12 @@ const tag = dryRun ? '[dry-run]' : ' ';
|
|
|
19
20
|
const log = (msg) => console.log(` ${tag} ${msg}`);
|
|
20
21
|
|
|
21
22
|
const INTEGRATIONS_DIR = process.env.INTEGRATIONS_DIR || 'integrations';
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
const PACKAGE_ROOT = resolve(import.meta.dirname, '..');
|
|
25
|
+
const localTools = resolve(process.cwd(), 'tools.json');
|
|
26
|
+
const TOOLS_PATH = existsSync(localTools) ? localTools : resolve(PACKAGE_ROOT, 'tools.json');
|
|
27
|
+
|
|
28
|
+
const tools = JSON.parse(await readFile(TOOLS_PATH, 'utf8'));
|
|
23
29
|
|
|
24
30
|
let finalScope = args.scope;
|
|
25
31
|
let finalHarness = args.harness;
|
package/scripts/list.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { readRules } from './lib/frontmatter.mjs';
|
|
5
|
+
|
|
6
|
+
const { values: args } = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
json: { type: 'boolean', default: false },
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Resolve paths (local vs package fallback)
|
|
13
|
+
const PACKAGE_ROOT = resolve(import.meta.dirname, '..');
|
|
14
|
+
const localRules = resolve(process.cwd(), 'rules');
|
|
15
|
+
const RULES_DIR =
|
|
16
|
+
process.env.RULES_DIR || (existsSync(localRules) ? localRules : resolve(PACKAGE_ROOT, 'rules'));
|
|
17
|
+
|
|
18
|
+
const rules = await readRules(RULES_DIR);
|
|
19
|
+
|
|
20
|
+
if (rules.length === 0) {
|
|
21
|
+
console.error(`❌ No rules found in ${RULES_DIR}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── JSON Output ───────────────────────────────────────────────────────
|
|
26
|
+
if (args.json) {
|
|
27
|
+
const output = rules.map((r) => ({
|
|
28
|
+
name: r.slug,
|
|
29
|
+
description: r.data.description || 'No description provided',
|
|
30
|
+
source: r.source,
|
|
31
|
+
}));
|
|
32
|
+
console.log(JSON.stringify(output, null, 2));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Pretty Terminal Output ────────────────────────────────────────────
|
|
37
|
+
// Find the longest name to align the columns perfectly
|
|
38
|
+
const maxSlugLength = Math.max(...rules.map((r) => r.slug.length));
|
|
39
|
+
|
|
40
|
+
console.log(`\n📚 \x1b[1mAvailable Rules\x1b[0m (${rules.length})\n`);
|
|
41
|
+
|
|
42
|
+
for (const rule of rules) {
|
|
43
|
+
const paddedSlug = rule.slug.padEnd(maxSlugLength);
|
|
44
|
+
// Using Cyan for the rule name
|
|
45
|
+
console.log(` \x1b[36m${paddedSlug}\x1b[0m ${rule.data.description || 'No description'}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('\n');
|