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 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 `opencode-rules` plugin).
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 `opencode-rules` plugin | `.opencode/rules/*.md` |
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
@@ -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[32mconvert\x1b[0m Compile rules/ into each harness's native format
33
- \x1b[32minstall\x1b[0m Install compiled artifacts into detected harnesses
34
- \x1b[32mvalidate\x1b[0m Validate each rule's frontmatter against the schema
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.4.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/_schema.json",
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,12 @@
1
+ ---
2
+ name: python
3
+ description: Rules for Python modules.
4
+ globs: ["**/*.py"]
5
+ tags: [language, python]
6
+ ---
7
+
8
+ # Python Rules
9
+
10
+ - Use type hints on public functions.
11
+ - Prefer `pathlib` over `os.path`.
12
+ - No wildcard imports.
@@ -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; };
@@ -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
- const RULES_DIR = process.env.RULES_DIR || 'rules';
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('tools.json', 'utf8'));
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
  );
@@ -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
- const tools = JSON.parse(await readFile('tools.json', 'utf8'));
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;
@@ -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');