diff --git a/aidd_docs/tasks/2026_10/2026_10_11_rule-contracts/official-contracts.md b/aidd_docs/tasks/2026_10/2026_10_11_rule-contracts/official-contracts.md index 49a44cd67..e9146506e 100644 --- a/aidd_docs/tasks/2026_10/2026_10_11_rule-contracts/official-contracts.md +++ b/aidd_docs/tasks/2026_10/2026_10_11_rule-contracts/official-contracts.md @@ -22,4 +22,4 @@ Native instruction text guides model behavior; it does not guarantee compliance. [6] [Codex 0.162.1 implementation](https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/core/src/agents_md.rs#L63) and [0.160.1 implementation](https://github.com/openai/codex/blob/d27764b82f7118f674371e6d6e76271d9d606edb/codex-rs/core/src/agents_md.rs#L63): separate global assembly and project budget. [HTTP receipts and hashes](evidence/official/http-receipts.json). -[7] [Skill host contract](../../../../plugins/aidd-context/skills/05-rule-generate/references/tool-paths.md) and [publication script](../../../../plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs): restricted serialization, body ownership and shared publication. +[7] [Skill host contract](../../../../plugins/aidd-context/skills/05-rule-generate/references/rule-publication.md) and [publication script](../../../../plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs): restricted serialization, body ownership and shared publication. diff --git a/docs/CATALOG.md b/docs/CATALOG.md index 4d4ec19b3..1f7b71f73 100644 --- a/docs/CATALOG.md +++ b/docs/CATALOG.md @@ -25,7 +25,7 @@ Bootstrap, project init, context-artifact generation, diagrams, learning, and ex | `02-project-memory` | Give the AI a memory of the project, wired into the tools you use | `01-scan`, `02-write`, `03-check`, `04-sync` | | `03-context-generate` | Router that dispatches an artifact-generation request to the dedicated generator below | delegates to `04`–`08` (and plugins / marketplaces) | | `04-skill-generate` | Generate a router-based skill across the host AI tool(s) | `01-scope`, `02-plan`, `03-write`, `04-validate` | -| `05-rule-generate` | Generate a coding rule that governs editor and agent behavior | `01-capture-rule`, `02-write-rule`, `03-validate` | +| `05-rule-generate` | Generate a coding rule that governs editor and agent behavior | `01-capture-rule`, `02-write-rule` | | `06-agent-generate` | Generate an agent across the host AI tool(s) | `01-capture-agent`, `02-write-agent`, `03-validate` | | `07-command-generate` | Generate a flat slash command across the host AI tool(s) | `01-capture-command`, `02-write-command`, `03-validate` | | `08-hook-generate` | Generate a lifecycle hook across the host AI tool(s) | `01-capture-hook`, `02-write-hook`, `03-validate` | diff --git a/plugins/aidd-context/CATALOG.md b/plugins/aidd-context/CATALOG.md index 06c1317db..176335539 100644 --- a/plugins/aidd-context/CATALOG.md +++ b/plugins/aidd-context/CATALOG.md @@ -118,10 +118,11 @@ Auto-generated index of skills, agents, references and assets shipped by the `ai |-------|------|---| | `actions` | [01-capture-rule.md](skills/05-rule-generate/actions/01-capture-rule.md) | - | | `actions` | [02-write-rule.md](skills/05-rule-generate/actions/02-write-rule.md) | - | -| `actions` | [03-validate.md](skills/05-rule-generate/actions/03-validate.md) | - | | `assets` | [rule-template.md](skills/05-rule-generate/assets/rule-template.md) | - | +| `references` | [rule-assessment.md](skills/05-rule-generate/references/rule-assessment.md) | - | | `references` | [rule-authoring.md](skills/05-rule-generate/references/rule-authoring.md) | - | -| `references` | [tool-paths.md](skills/05-rule-generate/references/tool-paths.md) | - | +| `references` | [rule-discovery.md](skills/05-rule-generate/references/rule-discovery.md) | - | +| `references` | [rule-publication.md](skills/05-rule-generate/references/rule-publication.md) | - | | `-` | [SKILL.md](skills/05-rule-generate/SKILL.md) | `Create, update, publish or delete a coding rule across host AI tools. Use when adding, changing or removing a rule, convention or coding standard. Not for skills, agents or hooks.` | #### `skills/06-agent-generate` diff --git a/plugins/aidd-context/skills/05-rule-generate/SKILL.md b/plugins/aidd-context/skills/05-rule-generate/SKILL.md index afdd94753..f8ee0c351 100644 --- a/plugins/aidd-context/skills/05-rule-generate/SKILL.md +++ b/plugins/aidd-context/skills/05-rule-generate/SKILL.md @@ -6,24 +6,32 @@ argument-hint: topic | auto # Rule Generate -Manage canonical rules and their active publications per confirmed host, or author a plugin source. +```mermaid +flowchart TD + request[Rule request] --> capture-rule + capture-rule -->|create/update| decision[Assessed candidate] + decision -->|revise/clarify| capture-rule + decision -->|keep/cancel| stop + decision -->|approved| write-rule + capture-rule -->|publish/delete confirmed| write-rule + write-rule -->|revised| capture-rule + write-rule -->|refused| stop + write-rule -->|verified| result +``` ## Actions -| # | Action | Role | Input | -| --- | -------------- | ----------------------------------------- | ------------ | -| 01 | `capture-rule` | Capture the topic, pick category and slug | user request | -| 02 | `write-rule` | Write the rule file per supported tool | the topic | -| 03 | `validate` | Check each rule file | the files | +Follow the flow. Read only the next action and its references. -Run the actions in order, `01 → 03`, and run each action's `## Test` before the next. -Before running an action, read its file in `actions/`, not only the table or assets. +| Action | Does | +| --- | --- | +| capture-rule | frame, compare, plan, decide | +| write-rule | write the approved result, verify | -## References +## Transversal rules -- `references/rule-authoring.md`: the contract (taxonomy, naming, frontmatter, content). -- `references/tool-paths.md`: per-tool rules path, frontmatter, unsupported tools, the gate. - -## Assets - -- `assets/rule-template.md`: rule file scaffold. +- Report unsupported hosts with alternatives; a lone AGENTS file identifies no installed host. +- Stop and name any installed reference that cannot be read. +- Change existing sources or publications only with explicit permission naming them. +- Write only after the user approves the candidate shown last. A choice, merge, edit or rename is not an approval: show the revision and wait. +- Keep or cancel leaves project bytes unchanged. diff --git a/plugins/aidd-context/skills/05-rule-generate/actions/01-capture-rule.md b/plugins/aidd-context/skills/05-rule-generate/actions/01-capture-rule.md index b3a5c48ff..ce6f18d17 100644 --- a/plugins/aidd-context/skills/05-rule-generate/actions/01-capture-rule.md +++ b/plugins/aidd-context/skills/05-rule-generate/actions/01-capture-rule.md @@ -1,29 +1,26 @@ # 01 - Capture rule -Settle the rule's topic and place before writing. +Frame the request, compare it with existing policies, plan the publication, decide with the user. ## Input -A rule topic (e.g. "TypeScript naming"), or `auto` to scan the codebase and propose rules. Empty means manual. +Request: a topic, `auto`, or a publish/delete. Default manual. ## Output -In-context: the topic, its category and slug, the file scope, a one-line description, and the write mode (host with supported tools, or plugin source). +Approved candidates with planned paths, or confirmed sources, operation and destinations. ## Process -1. **Gate.** Run the asset-access precheck ([tool-paths.md](../references/tool-paths.md)). - For an explicitly confirmed host publish/delete, identify the existing rule or rule set and hosts, then continue to Render in action 02; no new topic or body is needed. -2. **Auto or manual.** Ask whether to run auto or manual mode: - - **Auto**: scan the codebase, propose a rules architecture, wait for approval. - - **Manual**: ask the topic (blocking). Confirm any candidate first. -3. **Place.** Pick the category and slug from [rule-authoring.md](../references/rule-authoring.md). The category index drives the slug prefix. -4. **Scope.** State the glob the rule applies to, or note it applies to all files. Note a one-line description of what the rule governs, for tools whose scope frontmatter needs it. -5. **Write mode.** Ask where the rule goes: - - **Host project**: detect the installed tools ([tool-paths.md](../references/tool-paths.md)), propose the supported ones, and confirm which to target. Never pick one silently. - - **Plugin source**: confirm or create `plugins//rules/`. - -## Test - -- The topic, category and slug, or existing publish/delete selection, are confirmed in writing. -- Unsupported tools are named with what to do instead. +1. Confirm the operation and [destination](../references/rule-publication.md). + - Hosts: reuse the existing rule's hosts, else ask. Missing hosts: detect, propose installation, await selection before drafting. +2. Frame the topic. + - Manual: ask for it. + - Auto: scan, propose topics, await selection. + - Publish/delete: confirm sources and whole publications, then plan. +3. Draft with the [template](../assets/rule-template.md) and [authoring](../references/rule-authoring.md), once topic and [fields](../references/rule-publication.md#operations) are confirmed. +4. Compare: [discover policies](../references/rule-discovery.md) read-only in the project and loaded ancestors, never home or account scans, then [assess](../references/rule-assessment.md) each candidate. +5. Plan: for a host, resolve the installed writer to an absolute path, write the request outside the destinations, run it with `--plan` per [publication](../references/rule-publication.md). A refusal returns to step 3. +6. Present each candidate in full (body, description, scope, hosts), the planned paths and every finding (new rules too), then end the turn. + - Needs review: ask for evidence, reassess before offering source changes. + - Resolved: replan, present the revised candidate, end the turn again. diff --git a/plugins/aidd-context/skills/05-rule-generate/actions/02-write-rule.md b/plugins/aidd-context/skills/05-rule-generate/actions/02-write-rule.md index 88f705728..a8e705925 100644 --- a/plugins/aidd-context/skills/05-rule-generate/actions/02-write-rule.md +++ b/plugins/aidd-context/skills/05-rule-generate/actions/02-write-rule.md @@ -1,26 +1,27 @@ # 02 - Write rule -Build one canonical rule and publish it for each confirmed supported host. +Write the approved result and verify it. ## Input -From 01: the topic, category, slug, scope, and write mode. +Approval: a user message approving the last displayed candidate unchanged. + +Capture: the approved capture and its request. ## Output -The canonical source, generated targets, and any unsupported tool with its reason. +The written files, verified, or the refusal. ## Process -1. **Build.** For create/update, use [rule-template.md](../assets/rule-template.md); strip the scaffold. For publish/delete, skip to Render. -2. **Request.** For host create/update, put category, slug, description, optional paths and complete Markdown body into the JSON request in [tool-paths.md](../references/tool-paths.md). Keep YAML frontmatter out of the body. -3. **Render.** Per the write mode ([tool-paths.md](../references/tool-paths.md)): - - **Host**: resolve `scripts/write-rule.cjs` beside the loaded installed `SKILL.md`. Invoke its absolute path with explicit project, tools and the operation's arguments in [tool-paths.md](../references/tool-paths.md). Use it for create/update, publish and delete; never replace or remove generated files manually. It preflights all outputs. Report refusals verbatim; skip unsupported tools with their reason. - - **Plugin source**: write one canonical `.md` rule. No per-tool fan-out. -4. **Split.** When examples warrant it, write several rule files rather than one crowded one. -5. **Validate.** Check the script's reported targets against [tool-paths.md](../references/tool-paths.md). It must not require the AIDD CLI. +1. Match: for create/update, require that approval and compare body, fields, paths, resolution and evidence with the approved candidate. Without approval or on any difference, return to capture-rule, present the revision, end the turn, write nothing. +2. Write the approved result. + - Host: run the writer by the absolute path resolved in capture-rule, with the approved request. + - Plugin: write or delete the approved source only. +3. Verify: for a host, run the same absolute writer with `--check --publish`. Require no drift, deletions included. +4. Report the written files. On refusal or drift, stop and quote it verbatim. -## Test +## Rules -- Create/update/publish: canonical sources and active publications exist with the intended scope. -- Delete: the selected source and its publications are absent; remaining rules and user bytes survive. +- Leave generated files to the writer: canonical metadata and outputs are writer-owned, legacy rules unchanged. +- Claim consumption only with matching and nonmatching file evidence from fresh host sessions. diff --git a/plugins/aidd-context/skills/05-rule-generate/actions/03-validate.md b/plugins/aidd-context/skills/05-rule-generate/actions/03-validate.md deleted file mode 100644 index 75319de4b..000000000 --- a/plugins/aidd-context/skills/05-rule-generate/actions/03-validate.md +++ /dev/null @@ -1,23 +0,0 @@ -# 03 - Validate - -Check each written rule file against the contract. - -## Input - -The list of files written (from 02). - -## Output - -A short pass or fail line per rule file. - -## Process - -1. **Operation.** Create/update/publish: confirm expected sources and publications exist. Delete: confirm the selected source and its native files or shared rule body are absent; remaining rules and user bytes survive. -2. **Contract.** For remaining files, validate against [rule-authoring.md](../references/rule-authoring.md) and confirmed intent, including exact literals. -3. **Target.** Validate target path and frontmatter against [tool-paths.md](../references/tool-paths.md). - For Codex and OpenCode, each remaining body must appear once in the shared signed contribution; deleting the last shared rule removes that contribution. User guidance and memory bytes must survive, and rerunning the installed script with `--publish` must be byte-identical. Remaining native files retain complete bodies and scope syntax. Report publication separately from consumption; only real host evidence proves runtime use. -4. **Report.** Emit one pass/fail line per file. - -## Test - -Every affected rule has one pass/fail result for the requested operation and applicable contracts. diff --git a/plugins/aidd-context/skills/05-rule-generate/assets/rule-template.md b/plugins/aidd-context/skills/05-rule-generate/assets/rule-template.md index a2f28fa3f..cee8bc90c 100644 --- a/plugins/aidd-context/skills/05-rule-generate/assets/rule-template.md +++ b/plugins/aidd-context/skills/05-rule-generate/assets/rule-template.md @@ -1,8 +1,5 @@ -# + - +# -- <Concise rule preserving exact requirements.> -- <Another.> - -<!-- Several themes? Group the bullets under `## headings` (skip groups for a short rule). One tiny example per group, only if it removes ambiguity. --> +- <Requirement.> diff --git a/plugins/aidd-context/skills/05-rule-generate/references/rule-assessment.md b/plugins/aidd-context/skills/05-rule-generate/references/rule-assessment.md new file mode 100644 index 000000000..e53216154 --- /dev/null +++ b/plugins/aidd-context/skills/05-rule-generate/references/rule-assessment.md @@ -0,0 +1,22 @@ +# Rule assessment + +## Labels + +| Label | Means | +| --- | --- | +| needs review | uncertainty that could change the comparison; overrides every other label | +| new | no coverage, overlap or conflict | +| covered | existing policies supply every obligation throughout the scope | +| overlap | compatible partial repetition or extension | +| conflict | incompatible obligations, or an unjustified weakening | + +## Comparison + +- Compare obligations, prohibitions, scopes, conditions, exceptions and priority, prior sources on update and future files included. +- Exclude only scopes proven disjoint. +- Cite source paths, decisive quotes and scope reasons in every finding. + +## Choices + +- Offer create, keep, update, merge, scope, priority or cancel as applicable. +- Needs review: request evidence, reassess before offering source changes. Keep and cancel stay available. diff --git a/plugins/aidd-context/skills/05-rule-generate/references/rule-authoring.md b/plugins/aidd-context/skills/05-rule-generate/references/rule-authoring.md index 2012252ac..7b5bddd27 100644 --- a/plugins/aidd-context/skills/05-rule-generate/references/rule-authoring.md +++ b/plugins/aidd-context/skills/05-rule-generate/references/rule-authoring.md @@ -1,37 +1,30 @@ # Rule authoring -The contract every generated rule must satisfy. A rule governs editor and agent behavior for a set of files. - ## Rules -- **R1.** One rule file, one topic. Split a crowded rule into several files. -- **R2.** Bullets only. Keep each concise; preserve requested literals, conditions and exceptions over word limits. -- **R3.** Scope every rule to the files it applies to, or mark it as applying to all. -- **R4.** English only, regardless of conversation language. -- **R5.** Do not add a rule bullet that repeats, weakens, or contradicts another active bullet in the same rule. - -## File naming - -Format `#-slug[@version][-specificity]`. The `#` is the category index, single digit, no zero-pad. Examples: `2-typescript-naming`, `3-react@19-hooks`. +- R1. One topic per file; split multiple topics. +- R2. Concise bullets; keep literals, conditions and exceptions. +- R3. Explicit scope or all files. +- R4. English unless asked otherwise. +- R5. Distinct bullets: none repeats, weakens or contradicts another. -## Category taxonomy +## Naming -| # | Category | Covers | -| --- | -------------------------- | --------------------------------------------- | -| 00 | `architecture` | system patterns (Clean, Hexagonal, API design) | -| 01 | `standards` | code style, naming (camelCase, imports) | -| 02 | `programming-languages` | language-specific rules (TS strict mode) | -| 03 | `frameworks-and-libraries` | framework/lib patterns (React, Prisma) | -| 04 | `tooling` | tool/infra config (ESLint, Docker, CI) | -| 05 | `testing` | test patterns (fixtures, mocking) | -| 06 | `design-patterns` | code design (Repository, Factory) | -| 07 | `quality` | perf and security (caching, auth) | -| 08 | `domain` | business logic (entities, DTOs) | -| 09 | `other` | miscellaneous | +`#-slug[@version][-specificity]`: single-digit category prefix, no zero-padding. -Pick the most specific match: framework `03`, language syntax `02`, agnostic style `01`, architecture `00`, then `04`-`08` in order, else `09`. +| Index | Category | Covers | +| --- | --- | --- | +| 00 | architecture | system design | +| 01 | standards | style/naming | +| 02 | programming-languages | language rules | +| 03 | frameworks-and-libraries | framework/library patterns | +| 04 | tooling | tools/configuration | +| 05 | testing | test patterns | +| 06 | design-patterns | code design patterns | +| 07 | quality | performance/security | +| 08 | domain | business logic | +| 09 | other | miscellaneous | -## Content +Pick in order: `03`, `02`, `01`, `00`, `04`–`08`, else `09`. -- Group related bullets under a `## group` heading when the rule has several themes. Skip groups for a short rule. -- Add one tiny example per group only when it removes ambiguity. Good code only. +- Themes: `## group`, at most one tiny good-code example each. diff --git a/plugins/aidd-context/skills/05-rule-generate/references/rule-discovery.md b/plugins/aidd-context/skills/05-rule-generate/references/rule-discovery.md new file mode 100644 index 000000000..1e84e8f9e --- /dev/null +++ b/plugins/aidd-context/skills/05-rule-generate/references/rule-discovery.md @@ -0,0 +1,23 @@ +# Rule discovery + +- Hosts: native paths and scopes from `rule-publication.md`, canonical `aidd_docs/rules/**/*.md`, recorded targets, recursive native directories at relevant root and nested locations, and the guidance below. +- Plugin: confirmed rules, project authoring guidance, known companion policies. + +| Host | Additional project guidance | +| --- | --- | +| Claude | cwd/project ancestors/relevant descendants: `CLAUDE.md`, `.claude/CLAUDE.md`, `CLAUDE.local.md`; conditional cwd/ancestor `AGENTS.md`, `.claude/AGENTS.md`, nested `AGENTS.md`: verify version, built-in plugin, Project instructions mode, competing Claude files | +| Cursor | root/nested `AGENTS.md`, nested `.cursor/rules`; CLI also root `CLAUDE.md` | +| Copilot CLI | root/cwd/intermediate/relevant file-path descendants: `.github/copilot-instructions.md`, `AGENTS.md`, `CLAUDE.md`, `.claude/CLAUDE.md`, `GEMINI.md`; modular: no intermediate directories, matching `applyTo` required | +| Codex | root/nested: first nonempty `AGENTS.override.md`, `AGENTS.md`, configured fallbacks, in order | +| OpenCode | V2: project/upward/nested `AGENTS.md`; V1: nearest upward AGENTS, enabled `CLAUDE.md` fallback if absent, explicit project config `instructions` sources | + +- Include loaded ancestors, configured sources, future-file scopes and user text outside generated blocks. Claim no completeness. +- Existing policies: verify host and interface applicability; existence proves no activation. Unresolved cases go to `rule-assessment.md`. +- Deduplicate canonical body/scope renderings only with verified ownership, keeping every path. Keep differing native edits and unsigned policies separate. A missing publication leaves canonical intent in scope. +- Follow documented imports inside the repository, detect cycles, execute nothing. Relevant unsafe, external or symlink escapes, or uncertainty, need review. +- Copilot CLI imports `@relative-path` recursively from repository-wide and agent sources except GEMINI; never from modular, absolute or home-relative ones. Disabled sources are inactive; repository and agent sources have no general precedence. +- V1 plain references are not expanded. + +## Contracts + +[Claude](https://code.claude.com/docs/en/memory), [Cursor](https://cursor.com/docs/rules), [Cursor CLI](https://cursor.com/docs/cli/using#rules), [Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-custom-instructions), [Copilot support](https://docs.github.com/en/copilot/reference/custom-instructions-support), [Codex](https://learn.chatgpt.com/docs/agent-configuration/agents-md), [OpenCode V1](https://opencode.ai/docs/rules/), [OpenCode V2](https://opencode.ai/v2/docs/instructions/). diff --git a/plugins/aidd-context/skills/05-rule-generate/references/rule-publication.md b/plugins/aidd-context/skills/05-rule-generate/references/rule-publication.md new file mode 100644 index 000000000..af89c4aaf --- /dev/null +++ b/plugins/aidd-context/skills/05-rule-generate/references/rule-publication.md @@ -0,0 +1,31 @@ +# Rule publication + +## Publications + +| Host / ID | Target | Scope | +| --- | --- | --- | +| Claude / `claude` | `.claude/rules/<category>/<slug>.md` | YAML `paths` array; omit for all files | +| Cursor / `cursor` | `.cursor/rules/<category>/<slug>.mdc` | `description`, comma-joined `globs`, `alwaysApply: false`; all-files: no globs, true | +| Copilot / `copilot` | `.github/instructions/<NN>-<name>.instructions.md` | comma-joined `applyTo`; all-files `**` | +| Codex / `codex` | root `AGENTS.md` | shared signed `aidd_rules`, textual scope | +| OpenCode / `opencode` | root `AGENTS.md` | same contribution | + +Copilot `<name>` drops the slug's numeric prefix: category `01-standards`, slug `1-local-names` → `01-local-names.instructions.md`. + +## Operations + +Writer: absolute `scripts/write-rule.cjs` beside the loaded installed `SKILL.md`. Assume no checkout, project-relative or CLI path. + +Request JSON: category, slug, description, body; optional paths array, omitted for all files. Split comma globs; the writer refuses anything else invalid. + +```sh +node "$installed_rule_script" --project "$project_root" --tools "$confirmed_ids" --input "$request_json" +``` + +- Project: real absolute directory. Tools: comma-separated confirmed IDs. +- `--publish` replaces `--input`: all canonical sources, no identity argument, no subsets. +- `--delete category/slug` replaces `--input`: one source, all recorded publications; reports `files`. +- `--plan` on any of the three: runs every refusal, writes nothing, reports `{"dryRun":true,"files":[...]}` with the exact paths a real run touches. +- `--check --publish`: writes nothing, reports `{"drift":[...]}`, exits non-zero while a publication differs from its canonical source. +- Plugin: `plugins/<plugin>/rules/<category>/<slug>.md`; YAML `paths` for scope, or no frontmatter. No writer, project metadata or host fan-out. +- Physical deduplication does not ensure unique host context; local 32 KiB passes do not ensure the total instruction budget. diff --git a/plugins/aidd-context/skills/05-rule-generate/references/tool-paths.md b/plugins/aidd-context/skills/05-rule-generate/references/tool-paths.md deleted file mode 100644 index b8e6338b2..000000000 --- a/plugins/aidd-context/skills/05-rule-generate/references/tool-paths.md +++ /dev/null @@ -1,66 +0,0 @@ -# Tool paths (rules) - -This reference covers project rule generation only. Confirm hosts explicitly; a shared `AGENTS.md` does not identify installed tools. - -## Native targets - -| Tool | Script ID | Active surface | Scope | -| --- | --- | --- | --- | -| Claude Code | `claude` | `.claude/rules/<category>/<slug>.md` | `paths` YAML array; omitted for all files | -| Cursor | `cursor` | `.cursor/rules/<category>/<slug>.mdc` | `description`, comma-joined `globs`, `alwaysApply: false`; all files omit globs and use true | -| GitHub Copilot | `copilot` | `.github/instructions/<NN>-<name>.instructions.md` | comma-joined `applyTo`; `**` for all files | -| Codex | `codex` | root `AGENTS.md`, shared signed `aidd_rules` contribution | scopes are instructions to the model | -| OpenCode | `opencode` | the same contribution in root `AGENTS.md` | scopes are instructions to the model | - -Use taxonomy and `#-slug` naming in [rule-authoring.md](rule-authoring.md). Copilot drops the slug's single-digit prefix and adds the category's two-digit prefix, e.g. `01-standards/1-naming` becomes `01-naming.instructions.md`. - -Codex Markdown guidance is not a `.codex/rules` execution policy. OpenCode: write the root `AGENTS.md` only, never `.opencode/rules`. Native syntax tests prove rendering, not runtime consumption by every host. Codex/OpenCode selected together appear once in the shared file; other hosts may also discover AGENTS.md, so physical deduplication does not guarantee model-context deduplication across surfaces. - -Official contracts: [Claude Code rules](https://code.claude.com/docs/en/memory#organize-rules-with-clauderules), [Cursor rules](https://cursor.com/docs/rules) and [CLI loading](https://cursor.com/docs/cli/using#rules), [Copilot CLI instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-custom-instructions), [Codex AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md), [OpenCode rules](https://opencode.ai/docs/rules/) and [instructions](https://opencode.ai/v2/docs/instructions/). - -Publication does not force a running host to reload. Verify changed rules in a fresh session: Codex builds its instruction chain at startup; Copilot applies edits to a new or resumed session. OpenCode V2 refreshes ambient AGENTS.md before the next request, but already loaded nested guidance needs a new session. Claude project rules require the `project` settings source; Cursor rules concern Agent rather than Tab or Inline Edit. The writer preserves patterns without implementing each host's glob parser; a syntactically invalid or unmatched pattern can activate no files. Confirm representative matching and nonmatching files in the selected host. - -The shared contribution shows `Applies to:` followed by literal globs in Markdown code spans, or `all files`. Category/slug identity stays in canonical metadata rather than added visible headings. A leading ATX or single-line Setext title is kept without an added title; scope precedes the unchanged body. Otherwise the description supplies a level-two heading before scope and body. This bounded title check ignores fenced examples and indented code; it does not parse all Markdown. Complete body bytes and native rendering remain unchanged. - -## Installed script - -Resolve `scripts/write-rule.cjs` beside the loaded installed `SKILL.md` and invoke its absolute path with Node. This applies to native plugins and flat skills. Never reconstruct a plugin path, assume a framework checkout, project-relative script path, or installed AIDD CLI. The CommonJS script uses Node built-ins only and works inside ES module projects. - -Prepare a JSON request outside the project rule destinations: - -```json -{ - "category": "01-standards", - "slug": "1-naming", - "description": "Naming conventions", - "paths": ["src/**/*.ts", "test/**/*.ts"], - "body": "# Naming\n\n- Keep names clear.\n" -} -``` - -Use an absolute real project directory and confirmed comma-separated script IDs: - -```sh -node "$installed_rule_script" --project "$project_root" --tools claude,cursor,copilot,codex,opencode --input "$request_json" -node "$installed_rule_script" --project "$project_root" --tools claude,cursor,copilot,codex,opencode --publish -node "$installed_rule_script" --project "$project_root" --tools claude,cursor,copilot,codex,opencode --delete 01-standards/1-naming -``` - -Create/update records confirmed targets in `aidd_docs/rules/<category>/<slug>.md`. Publish synchronizes the named native hosts and the complete shared contribution from canonical targets; delete also cleans the deleted rule's previous targets. Updating targets cleans removed targets. JSON accepts exactly category, slug, description, optional paths and body. Empty/omitted paths mean all files. Reject comma-containing globs (including brace alternations) rather than serialize ambiguous native lists; supply separate globs. Description is one line. Body is preserved completely and must not begin with YAML frontmatter. Scope belongs in JSON, never an arbitrary YAML parser. - -## Canonical and output ownership - -Canonical sources start with one strict script-owned JSON comment recording version, category, slug, description, paths and targets. Do not edit its syntax, key order or target order by hand. A body may be edited manually beneath intact metadata, then explicitly published. Generated native files carry a content signature; prior canonical rendering equality also proves ownership for an unsigned existing output. A differing unowned file, edited native file, or edited/duplicate/incomplete shared contribution refuses the entire request before writes. Restore the intact generated output and move edits into canonical sources. Hashes detect edits; they are not an authentication boundary against someone deliberately recomputing them. - -The script validates the complete prospective set before changing canonical sources or outputs. It rejects symlink targets/ancestors, traversal, reserved rule markers outside fenced examples and unclosed fences. Requests and existing files must be valid UTF-8; parsed text must roundtrip without unpaired Unicode surrogates. Valid Unicode pairs and complete bodies are preserved. The shared signature covers both payload and separator ownership; editing either refuses the request. It preserves every byte outside its shared contribution, including CRLF and `aidd_project_memory`. Repeated publication is byte-idempotent. Individual file replacement is atomic; this is not a multi-file transaction against disk failures or concurrent writers. - -Root `AGENTS.override.md` blocks Codex generation because it masks AGENTS.md. The script conservatively refuses a local AGENTS.md exceeding the default 32 KiB project-document budget when Codex is involved. Other discovered project instruction files share that configurable budget; the writer does not inspect them or host configuration. Global/home user guidance is assembled separately, outside this project-document counter in verified Codex 0.160.1 and 0.162.1 ([official implementation](https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/core/src/agents_md.rs#L63)). A local pass cannot guarantee the remaining project budget, and the script does not change host configuration. - -No migration: old `.opencode/rules` or `.codex/rules` files are neither imported nor deleted. An old `aidd_opencode_rules` block causes `Ambiguous or legacy AIDD rule contribution; no automatic migration.` or the corresponding incomplete-block error. Explicitly resolve historical guidance before retrying; do not silently erase or import it. Flat archive distribution of standalone rules remains out of scope. - -## Write modes - -- **Host project**: use the installed script above for explicitly confirmed hosts. -- **Plugin source**: author one canonical Markdown file under `plugins/<plugin>/rules/<category>/<slug>.md`; retain a `paths` array for scoped rules or omit frontmatter for all files. No project canonical metadata, script publication or host fan-out in this mode. - -Before either mode, confirm this reference is readable; otherwise report the missing installed asset. Never choose a mode or host silently. diff --git a/plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs b/plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs index e7f1d0c9c..ba341479d 100644 --- a/plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs +++ b/plugins/aidd-context/skills/05-rule-generate/scripts/write-rule.cjs @@ -25,12 +25,13 @@ function options(args) { const result = {}; for (let i = 0; i < args.length; i++) { const key = args[i]; - if (!["--project", "--tools", "--input", "--delete", "--publish"].includes(key) || key in result) fail(`Unknown or repeated option: ${key}`); - result[key] = key === "--publish" ? true : args[++i]; + if (!["--project", "--tools", "--input", "--delete", "--publish", "--plan", "--check"].includes(key) || key in result) fail(`Unknown or repeated option: ${key}`); + result[key] = ["--publish", "--plan", "--check"].includes(key) ? true : args[++i]; if (!result[key] || (typeof result[key] === "string" && result[key].startsWith("--"))) fail(`Missing value: ${key}`); } if (!result["--project"] || !result["--tools"]) fail("Explicit --project and --tools are required."); if (["--input", "--delete", "--publish"].filter((key) => key in result).length !== 1) fail("Choose exactly one of --input, --delete or --publish."); + if (result["--check"] && (!result["--publish"] || result["--plan"])) fail("--check verifies --publish alone and excludes --plan."); return result; } @@ -263,6 +264,12 @@ function main() { } // Preflight every destination and ownership above; no validation can first happen after mutation. for (const relative of writes.keys()) target(relative); + if (opts["--plan"]) return void process.stdout.write(JSON.stringify({ dryRun: true, files: [...writes.keys()] }) + "\n"); + if (opts["--check"]) { + const drift = [...writes].filter(([relative, text]) => text !== read(relative)).map(([relative]) => relative); + process.exitCode = drift.length ? 1 : 0; + return void process.stdout.write(JSON.stringify({ drift }) + "\n"); + } for (const [relative, text] of writes) { const file = target(relative); if (text === read(relative)) continue; diff --git a/scripts/__tests__/rule-generation.test.js b/scripts/__tests__/rule-generation.test.js index a387f554f..dd6d1ac0b 100644 --- a/scripts/__tests__/rule-generation.test.js +++ b/scripts/__tests__/rule-generation.test.js @@ -389,3 +389,72 @@ test("valid Unicode pairs and CRLF body survive installed generation and publica assert.equal(f.run(null, ALL, ["--publish"]).status, 0); assert.deepEqual(f.snapshot(), before); }); + +const json = (result) => JSON.parse(result.stdout); + +test("--plan lists exact publication paths for create, update and delete without touching any byte", (t) => { + const f = fixture(t); + const before = f.snapshot(); + const planned = f.run(rule, ALL, ["--plan", "--input", path.join(f.dir, "input.json")]); + assert.equal(planned.status, 0, planned.stderr); + assert.equal(json(planned).dryRun, true); + assert.deepEqual(json(planned).files.sort(), ["AGENTS.md", ".claude/rules/01-standards/1-naming.md", ".cursor/rules/01-standards/1-naming.mdc", ".github/instructions/01-naming.instructions.md", "aidd_docs/rules/01-standards/1-naming.md"].sort()); + assert.deepEqual(f.snapshot(), before); + assert.equal(f.run().status, 0); + const published = f.snapshot(); + const removal = f.run(null, ALL, ["--plan", "--delete", "01-standards/1-naming"]); + assert.equal(removal.status, 0, removal.stderr); + assert.ok(json(removal).files.includes(".claude/rules/01-standards/1-naming.md")); + assert.deepEqual(f.snapshot(), published); +}); + +test("--plan refuses exactly what the real run refuses and still changes nothing", (t) => { + const f = fixture(t); + const before = f.snapshot(); + const bad = f.run({ ...rule, slug: "naming" }, ALL, ["--plan", "--input", path.join(f.dir, "input.json")]); + assert.equal(bad.status, 1); + assert.match(bad.stderr, /Invalid taxonomy category or matching #-slug/); + const absent = f.run(null, ALL, ["--plan", "--delete", "01-standards/1-naming"]); + assert.equal(absent.status, 1); + assert.match(absent.stderr, /Cannot delete an absent canonical rule/); + assert.deepEqual(f.snapshot(), before); +}); + +test("--check passes on an idempotent publication and names every drifting file otherwise", (t) => { + const f = fixture(t); + assert.equal(f.run().status, 0); + const before = f.snapshot(); + const clean = f.run(null, ALL, ["--check", "--publish"]); + assert.equal(clean.status, 0, clean.stderr); + assert.deepEqual(json(clean).drift, []); + const source = "aidd_docs/rules/01-standards/1-naming.md"; + f.put(source, f.read(source).replace(rule.body, "- Manual source edit.\n")); + const edited = f.snapshot(); + const drifted = f.run(null, ALL, ["--check", "--publish"]); + assert.equal(drifted.status, 1); + assert.ok(json(drifted).drift.includes(".claude/rules/01-standards/1-naming.md")); + assert.ok(json(drifted).drift.includes("AGENTS.md")); + assert.deepEqual(f.snapshot(), edited); + assert.notDeepEqual(edited, before); +}); + +test("--check holds after a deletion that emptied the publications", (t) => { + const f = fixture(t); + assert.equal(f.run().status, 0); + assert.equal(f.run(null, ALL, ["--delete", "01-standards/1-naming"]).status, 0); + const after = f.snapshot(); + const checked = f.run(null, ALL, ["--check", "--publish"]); + assert.equal(checked.status, 0, checked.stderr); + assert.deepEqual(json(checked).drift, []); + assert.deepEqual(f.snapshot(), after); +}); + +test("--plan and --check accept only the operations they can simulate", (t) => { + const f = fixture(t); + assert.equal(f.run().status, 0); + for (const extra of [["--check", "--delete", "01-standards/1-naming"], ["--check", "--plan", "--publish"], ["--plan"], ["--check"], ["--plan", "--plan", "--publish"]]) { + const result = f.run(null, ALL, extra); + assert.equal(result.status, 1, extra.join(" ")); + assert.equal(result.stdout, ""); + } +});