@ainova-systems/intelligence 0.11.0-rc.7 → 0.11.0-rc.9

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.
Files changed (51) hide show
  1. package/README.md +19 -16
  2. package/cli/commands/adapter.sh +153 -0
  3. package/cli/commands/init.sh +113 -22
  4. package/cli/commands/package.sh +30 -0
  5. package/cli/commands/registry.sh +4 -2
  6. package/cli/commands/status.sh +12 -4
  7. package/cli/commands/sync.sh +7 -20
  8. package/cli/commands/update.sh +76 -70
  9. package/cli/engine-package.yaml +2 -2
  10. package/cli/intelligence +10 -12
  11. package/cli/{commands/doctor.sh → internal/check.sh} +30 -23
  12. package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +117 -30
  13. package/cli/{commands/add.sh → internal/package-add.sh} +11 -16
  14. package/cli/{commands/list.sh → internal/package-list.sh} +9 -4
  15. package/cli/{commands/remove.sh → internal/package-remove.sh} +3 -3
  16. package/cli/{commands/search.sh → internal/package-search.sh} +4 -4
  17. package/cli/internal/package-update.sh +103 -0
  18. package/cli/{commands/install.sh → internal/restore.sh} +7 -18
  19. package/cli/internal/target-state.sh +57 -0
  20. package/cli/internal/upgrade-v2.sh +133 -0
  21. package/cli/lib/cli-common.sh +142 -12
  22. package/cli/lib/lockfile.sh +1 -1
  23. package/cli/lib/manifest.sh +109 -0
  24. package/cli/lib/registry.sh +4 -4
  25. package/engine/ENGINE_SHA +1 -1
  26. package/engine/adapters/_template.sh +10 -9
  27. package/engine/adapters/agents.sh +11 -11
  28. package/engine/adapters/opencode.sh +1 -1
  29. package/engine/lib/common.sh +14 -22
  30. package/engine/lib/contract.sh +14 -14
  31. package/engine/sync.sh +25 -20
  32. package/package.json +1 -1
  33. package/packages/sync/agents/intelligence-architect.md +5 -3
  34. package/packages/sync/agents/intelligence-operator.md +10 -13
  35. package/packages/sync/references/adapters.md +252 -0
  36. package/packages/sync/references/conventions.md +385 -0
  37. package/packages/sync/rules/intelligence-authoring.md +6 -6
  38. package/packages/sync/skills/intelligence-add-agent/SKILL.md +5 -5
  39. package/packages/sync/skills/intelligence-add-rule/SKILL.md +3 -3
  40. package/packages/sync/skills/intelligence-add-skill/SKILL.md +3 -3
  41. package/packages/sync/skills/intelligence-extract-skill/SKILL.md +2 -2
  42. package/packages/sync/skills/intelligence-install-adapter/SKILL.md +29 -22
  43. package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +3 -3
  44. package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
  45. package/packages/sync/skills/intelligence-review-skills/SKILL.md +6 -6
  46. package/packages/sync/skills/intelligence-sync/SKILL.md +13 -9
  47. package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +19 -37
  48. package/packages/sync/skills/intelligence-update/SKILL.md +34 -156
  49. package/cli/commands/upgrade.sh +0 -68
  50. package/packages/sync/docs/ADAPTERS.md +0 -214
  51. package/packages/sync/docs/CONVENTIONS.md +0 -456
package/engine/sync.sh CHANGED
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # CONFIG_FILE the project's manifest (intelligence.yaml at the root)
9
9
  # REPO_ROOT the project root
10
- # IS_UMBRELLA_REL the project's content dir, repo-relative
10
+ # IS_CONTENT_REL the project's content dir, repo-relative
11
11
  # IS_MODULE_REL the installed sync package, repo-relative
12
12
  # IS_PROTECTED_DIRS dirs an adapter output may never overlap
13
13
  #
@@ -35,21 +35,21 @@ _vc_rc=0
35
35
  check_version_compat "$_cf" || _vc_rc=$?
36
36
  if [ "$_vc_rc" -ne 0 ]; then exit "$_vc_rc"; fi
37
37
 
38
- # Schema gap → refuse. sync is a PURE synchronizer: it never migrates, so a
39
- # project behind this engine must be brought forward by `intelligence upgrade`
40
- # first. An ABSENT stamp means the same thing — a manifest with no
41
- # `sync_version` must not silently sync past a schema change.
42
- _stamp="$(read_engine_stamp "$_cf")"
38
+ # Schema gap → refuse. sync is a PURE synchronizer: it never changes schemas,
39
+ # so the CLI lifecycle preflight must align the project first. An ABSENT stamp
40
+ # means the same thing — a manifest with no
41
+ # `schema_version` must not silently sync past a schema change.
42
+ _stamp="$(read_schema_version "$_cf")"
43
43
  _eng="$(engine_version)"
44
44
  if [ -z "$_stamp" ]; then
45
- is_status needs-update "stamped= engine=$_eng (no sync_version)"
46
- echo "ERROR: the manifest has no sync_version — schema un-applied." >&2
47
- echo " Run: intelligence upgrade" >&2
45
+ is_status needs-update "stamped= engine=$_eng (no schema_version)"
46
+ echo "ERROR: the manifest has no schema_version — schema un-applied." >&2
47
+ echo " Run: intelligence init --apply" >&2
48
48
  exit "$IS_RC_NEEDS_UPDATE"
49
49
  elif [ -n "$_eng" ] && _ver_gt "$_eng" "$_stamp"; then
50
50
  is_status needs-update "stamped=$_stamp engine=$_eng"
51
51
  echo "ERROR: project at $_stamp but engine is $_eng — pending schema changes." >&2
52
- echo " Run: intelligence upgrade" >&2
52
+ echo " Run: intelligence init --apply" >&2
53
53
  exit "$IS_RC_NEEDS_UPDATE"
54
54
  fi
55
55
 
@@ -64,19 +64,19 @@ CONFIG_FILE="$(cd "$(dirname "$CONFIG_FILE")" && pwd)/$(basename "$CONFIG_FILE")
64
64
 
65
65
  # Layout tokens for generated output (see finalize_output_file in common.sh).
66
66
  # Package-shipped rules/agents cannot hardcode the content dir's name — the
67
- # project chooses it — so they write `<umbrella>` / `<module>` and every adapter
67
+ # project chooses it — so they write `<content-dir>` / `<module>` and every adapter
68
68
  # expands them on the way out.
69
- IS_UMBRELLA_REL="${IS_UMBRELLA_REL:-intelligence}"
69
+ IS_CONTENT_REL="${IS_CONTENT_REL:-intelligence}"
70
70
  # No vendor default: the CLI always exports the package path (derived from its
71
71
  # distribution data), so a bare invocation must say so rather than guess.
72
72
  if [ -z "${IS_MODULE_REL:-}" ]; then
73
73
  echo "ERROR: the engine needs IS_MODULE_REL (exported by the intelligence CLI)." >&2
74
74
  exit 1
75
75
  fi
76
- export IS_UMBRELLA_REL IS_MODULE_REL
76
+ export IS_CONTENT_REL IS_MODULE_REL
77
77
 
78
- # Project-owned adapters live in the content dir: <umbrella>/adapters/.
79
- INTELLIGENCE_DIR="$REPO_ROOT/$IS_UMBRELLA_REL"
78
+ # Project-owned adapters live in the content dir: <content-dir>/adapters/.
79
+ INTELLIGENCE_DIR="$REPO_ROOT/$IS_CONTENT_REL"
80
80
 
81
81
  TARGET_FILTER="${1:-}"
82
82
 
@@ -132,8 +132,8 @@ done
132
132
  # Adapters come from two places, discovered by filename (minus `.sh`,
133
133
  # `_template` excluded):
134
134
  #
135
- # 1. Built-in — shipped inside the CLI, replaced wholesale on every upgrade
136
- # 2. Project — <umbrella>/adapters/, owned by the project and never touched
135
+ # 1. Built-in — shipped inside the CLI, replaced with each CLI installation
136
+ # 2. Project — <content-dir>/adapters/, owned by the project and never touched
137
137
  #
138
138
  # A custom adapter therefore belongs in the content dir's `adapters/`; the
139
139
  # built-in directory lives inside the installed CLI and is not the project's to
@@ -183,7 +183,12 @@ while [ "$adapter_idx" -lt "$adapter_count" ]; do
183
183
 
184
184
  # Check if target is enabled in config
185
185
  enabled=$(is_target_enabled "$CONFIG_FILE" "$adapter")
186
- if [ "$enabled" != "1" ] && [ -z "$TARGET_FILTER" ]; then
186
+ if [ "$enabled" != "1" ]; then
187
+ if [ -n "$TARGET_FILTER" ]; then
188
+ echo "ERROR: Adapter '$TARGET_FILTER' is disabled in $CONFIG_FILE." >&2
189
+ echo " Enable it first: intelligence adapter enable $TARGET_FILTER" >&2
190
+ exit 1
191
+ fi
187
192
  continue
188
193
  fi
189
194
 
@@ -227,7 +232,7 @@ warn_unsynced "$REPO_ROOT" "$CONFIG_FILE"
227
232
  report_model_drift "$CONFIG_FILE"
228
233
 
229
234
  echo ""
230
- # sync.sh never migrates (`intelligence upgrade` owns that), so success is
231
- # always ok.
235
+ # sync.sh never changes project schemas (the CLI preflight owns that), so
236
+ # success is always ok.
232
237
  is_status ok "synced=$synced"
233
238
  echo "=== Done: $synced target(s) synced ==="
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.11.0-rc.7",
3
+ "version": "0.11.0-rc.9",
4
4
  "description": "Build, version and distribute AI agent intelligence across your organization — one CLI, versioned Intelligence Packages, and a sync engine for Claude Code, Cursor, Copilot, Codex, Pi and OpenCode.",
5
5
  "bin": {
6
6
  "intelligence": "bin/intelligence.js"
@@ -9,12 +9,13 @@ skills:
9
9
  - intelligence-add-skill
10
10
  - intelligence-extract-skill
11
11
  - intelligence-review-skills
12
+ - intelligence-learn-from-repository
12
13
  - intelligence-learn-from-context
13
14
  ---
14
15
 
15
16
  # Intelligence architect
16
17
 
17
- Owns `<umbrella>/` itself: what the layer is made of, and what it is allowed to grow into. Not the project's code — the instructions that shape how everyone else writes it.
18
+ Owns `<content-dir>/` itself: what the layer is made of, and what it is allowed to grow into. Not the project's code — the instructions that shape how everyone else writes it.
18
19
 
19
20
  ## Expertise
20
21
 
@@ -45,9 +46,10 @@ The per-artifact checks are procedure, so they live in the meta-skills rather th
45
46
  | `intelligence-add-rule` / `intelligence-add-agent` / `intelligence-add-skill` | author one artifact |
46
47
  | `intelligence-extract-skill` | turn an observed workflow into a skill |
47
48
  | `intelligence-review-skills` | audit the layer for duplication, drift, size, hardcoded paths |
49
+ | `intelligence-learn-from-repository` | propose the initial project-owned layer from repository evidence |
48
50
  | `intelligence-learn-from-context` | fold a session's lessons back into the layer |
49
51
  | `intelligence-sync` | project the source to every tool channel |
50
- | `intelligence-update` | update or migrate the engine |
51
- | `intelligence-install-adapter` / `intelligence-uninstall-adapter` | turn a tool channel on or off |
52
+ | `intelligence-update` | interpret and apply the CLI's unified update plan |
53
+ | `intelligence-install-adapter` / `intelligence-uninstall-adapter` | research and manage a tool adapter |
52
54
 
53
55
  A change is done when the sync is green and the skill you invoked reports clean. Size is a separate judgement: the caps are ceilings, not quotas, and a short artifact is not a defect.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: intelligence-operator
3
- description: "Run the engine's mechanical flows - sync, update, adapter install and removal"
3
+ description: "Interpret CLI plans and operate sync, update, and adapter flows"
4
4
  tier: standard
5
5
  access: full
6
6
  skills:
@@ -12,20 +12,17 @@ skills:
12
12
 
13
13
  # Intelligence operator
14
14
 
15
- Operates the engine: projects the source tree to every enabled tool channel, updates or migrates the
16
- engine, turns tool channels on and off. What the layer contains - which rules, agents and skills
15
+ Operates the CLI: projects the source tree to every enabled tool channel, interprets and applies update
16
+ plans, and manages adapters. What the layer contains - which rules, agents and skills
17
17
  exist and what they say - is `intelligence-architect`'s judgement; this agent runs the machinery
18
18
  that ships it.
19
19
 
20
20
  ## Expertise
21
21
 
22
- The status contract (`<module>/docs/CONVENTIONS.md`, Migration & Module Contract). Every flow
23
- here is deterministic and fail-closed: the sync is a pure synchronizer that refuses across an
24
- un-applied schema, the update flow stages, verifies and only then commits, and every flow
25
- reports `IS_STATUS`. The work is running the right flow, reading the code it returns, and doing
26
- what that code says - including stopping. The guards carry the decisions, which is why this agent
27
- runs on the standard tier: the failure mode is a loud refusal and a retry, not a plausible wrong
28
- answer.
22
+ The CLI and engine status contract. Mechanical flows are deterministic and
23
+ fail-closed; the agent reads their result, selects the next supported command,
24
+ checks changelog post-conditions, and stops on a refusal instead of recreating
25
+ the operation in prose.
29
26
 
30
27
  ## Boundaries
31
28
 
@@ -35,7 +32,7 @@ answer.
35
32
  - **Operating is not authoring.** A change to what an artifact says - a rule body, an agent persona,
36
33
  a skill's steps - belongs to `intelligence-architect` and the authoring meta-skills. This agent
37
34
  ships what exists and never decides what should exist.
38
- - **A state the engine refuses to resolve goes to a human.** `ambiguous` and every other non-ok
35
+ - **A state the CLI refuses to resolve goes to a human.** `ambiguous` and every other non-ok
39
36
  status exists so that nobody guesses past a refusal; report the code and its detail, do the one
40
37
  thing the contract names for it, and stop rather than force a way through.
41
38
 
@@ -45,5 +42,5 @@ answer.
45
42
  <sync-cmd> # expect IS_STATUS=ok
46
43
  ```
47
44
 
48
- Done is the invoked skill reporting its own success criteria met: `IS_STATUS=ok` (`migrated` counts,
49
- for an update), per-target counts matching the sources, and no warning left unhandled.
45
+ Done is the invoked skill reporting its own success criteria met: `IS_STATUS=ok`,
46
+ per-target counts matching the sources, and no warning left unhandled.
@@ -0,0 +1,252 @@
1
+ # Writing an Intelligence Adapter
2
+
3
+ An adapter transforms tool-neutral rules, agents and skills into one tool's native files. The sync engine discovers adapters by filename and calls one shell function for each enabled target.
4
+
5
+ ## Built-in and project adapters
6
+
7
+ | Location | Owner | Upgrade behavior |
8
+ |---|---|---|
9
+ | Installed CLI's `engine/adapters/` | Intelligence | Replaced when the CLI is upgraded |
10
+ | `<content-dir>/adapters/` | Project | Never changed by CLI upgrades |
11
+
12
+ The default content directory is `intelligence/`; `project.intelligence_dir` in `intelligence.yaml` may choose another. A project adapter with the same name as a built-in overrides it, and sync reports the override.
13
+
14
+ Contribute broadly useful integrations as built-ins. Keep organization-specific or experimental formats in the project.
15
+
16
+ ## Create and enable an adapter
17
+
18
+ Choose a lowercase shell-safe name matching `[a-z][a-z0-9_]*`, then scaffold from the template bundled with the installed engine:
19
+
20
+ ```bash
21
+ intelligence adapter create mytool
22
+ ```
23
+
24
+ This creates `<content-dir>/adapters/mytool.sh` and refuses to overwrite an existing file. Implement the generated functions, then enable its target:
25
+
26
+ ```bash
27
+ intelligence adapter enable mytool
28
+ ```
29
+
30
+ `adapter enable` adds or updates this manifest entry when the adapter exists, then runs a full sync so shared outputs such as `AGENTS.md` remain current:
31
+
32
+ ```yaml
33
+ targets:
34
+ mytool: { enabled: true, output: ".mytool" }
35
+ ```
36
+
37
+ Change `output` in `intelligence.yaml` if the tool expects another location. The engine validates the resolved path before calling the adapter.
38
+
39
+ To stop syncing a target:
40
+
41
+ ```bash
42
+ intelligence adapter disable mytool
43
+ ```
44
+
45
+ Disabling changes only target state. Generated output is deliberately kept because a generic command cannot know which paths a custom adapter owns. Remove only documented owned paths after reviewing them. A disabled project adapter can be deleted with `intelligence adapter remove mytool`; removal prompts by default, accepts `--apply` for explicit non-interactive use, and also keeps generated output. Built-in adapter source cannot be removed. Use `intelligence adapter list` to inspect source, state and output.
46
+
47
+ ## Required function
48
+
49
+ The file name and function name form the adapter's identity:
50
+
51
+ ```bash
52
+ # <content-dir>/adapters/mytool.sh
53
+ sync_to_mytool() {
54
+ local repo_root="$1"
55
+ local config_file="$2"
56
+ local output_dir="$3"
57
+
58
+ # transform sources into output_dir
59
+ }
60
+ ```
61
+
62
+ The engine calls:
63
+
64
+ ```text
65
+ sync_to_<name> <repo-root> <absolute-intelligence.yaml> <absolute-output-path>
66
+ ```
67
+
68
+ The engine has already loaded `engine/lib/common.sh` before it sources the adapter, so project adapters use its functions directly.
69
+
70
+ ## Source model
71
+
72
+ `sources.rules`, `sources.agents` and `sources.skills` contain repo-relative directory paths. Packages have already been resolved, fetched and pinned by the CLI, so package content appears as an ordinary path under `.intelligence/packages/`. Adapters never perform network access or parse `packages:`.
73
+
74
+ Iterate a source section in manifest order:
75
+
76
+ ```bash
77
+ while IFS= read -r src; do
78
+ [ -n "$src" ] || continue
79
+ dir="$(resolve_source_dir "$repo_root" "$src")"
80
+ [ -d "$dir" ] || continue
81
+
82
+ for file in "$dir"/*.md; do
83
+ [ -f "$file" ] || continue
84
+ # transform file
85
+ done
86
+ done < <(read_yaml_list "$config_file" "rules")
87
+ ```
88
+
89
+ Later sources intentionally overwrite same-named artifacts from earlier sources. Package sources are wired before project sources, so project content can override a package artifact by name.
90
+
91
+ ## Shared functions
92
+
93
+ Use the engine library instead of copying parsers or file-handling logic.
94
+
95
+ | Function | Purpose |
96
+ |---|---|
97
+ | `resolve_source_dir(repo_root, source)` | Resolve a manifest source to its local directory. |
98
+ | `read_yaml_list(config, section)` | Stream entries from `sources.<section>`. |
99
+ | `get_frontmatter_value(key, file)` | Read a scalar from the first frontmatter block. |
100
+ | `has_frontmatter(file)` / `has_paths(file)` | Inspect source shape. |
101
+ | `strip_frontmatter(file)` | Emit the body without its first frontmatter block. |
102
+ | `get_model(config, tool, tier)` | Resolve a `heavy`, `standard` or `light` model, including manifest overrides. |
103
+ | `get_model_default(tool, tier)` | Read the built-in model default. |
104
+ | `copy_skill_bundle(src, dest)` | Copy `SKILL.md` and all resources safely, normalize Markdown and quote free-text frontmatter. |
105
+ | `sync_open_skill_dirs(root, config, dest)` | Own and populate a shared Agent Skills directory such as `.agents/skills/`. |
106
+ | `finalize_output_file(file)` | Expand layout tokens and normalize line endings; required for every emitted text file. |
107
+ | `get_target_field(config, target, field)` | Read another field from the target configuration. |
108
+ | `repo_rel_link(root, path)` | Produce a stable repo-relative link for a committed output. |
109
+
110
+ `lint_frontmatter` is run across all inputs by the engine before adapters execute. It warns about common YAML hazards; adapters should not duplicate that pass.
111
+
112
+ ## Rules, skills and agents
113
+
114
+ ### Rules
115
+
116
+ Rules without `paths:` are always-on. Rules with `paths:` are scoped.
117
+
118
+ Intelligence routes always-on rules once through `AGENTS.md` for tools that consume it. An adapter for such a tool should not copy those rules again. If the tool has a native scoped-rule channel, transform `paths:` to the tool's equivalent.
119
+
120
+ | Built-in | Always-on rules | Scoped rules |
121
+ |---|---|---|
122
+ | `agents` | Inline in `AGENTS.md` | List with links |
123
+ | `claude` | Copy | Copy with `paths:` preserved |
124
+ | `cursor` | Omit; reads `AGENTS.md` | `.mdc` with `globs:` |
125
+ | `copilot` | Omit; reads `AGENTS.md` | `.instructions.md` with `applyTo:` |
126
+ | `codex` | Omit; reads `AGENTS.md` | No native channel |
127
+ | `pi` | Omit; reads `AGENTS.md` | Generated on-demand files and extension |
128
+ | `opencode` | Omit; reads `AGENTS.md` | No native channel |
129
+
130
+ If a new adapter relies on `AGENTS.md` for always-on rules, its target must require `agents`. Add that invariant to `engine/sync.sh` when contributing the adapter upstream.
131
+
132
+ ### Skills
133
+
134
+ Skills follow the [Agent Skills standard](https://agentskills.io). Copy each skill directory as a complete bundle—not only `SKILL.md`—because its body may reference `references/`, `scripts/` or `assets/` beside it.
135
+
136
+ ```bash
137
+ copy_skill_bundle "$source_skill_dir" "$output_dir/skills/$skill_name"
138
+ ```
139
+
140
+ Do not use plain `cp` for skill bundles. `copy_skill_bundle` preserves non-Markdown assets, avoids materializing symlink targets, normalizes Markdown, expands layout tokens and quotes `description` and `argument-hint` where strict YAML readers require strings.
141
+
142
+ Codex, Pi and OpenCode share `.agents/skills/`. Any adapter writing that open-standard directory must call `sync_open_skill_dirs`; it is the single lifecycle owner for immediate skill subdirectories.
143
+
144
+ ### Agents
145
+
146
+ Source agents use tool-neutral fields:
147
+
148
+ ```yaml
149
+ ---
150
+ name: backend-developer
151
+ description: "Implements backend features"
152
+ tier: heavy
153
+ access: full
154
+ ---
155
+ ```
156
+
157
+ Map `tier` through `get_model`, not a hardcoded model name. Transform `access: full|readonly` into the target tool's native permission model. Preserve the body as the agent's instructions.
158
+
159
+ Built-ins currently emit Claude and Cursor Markdown, Copilot `.agent.md`, Codex TOML, Pi prompt templates and OpenCode Markdown subagents. Read the closest built-in adapter before implementing a new transformation.
160
+
161
+ ## Layout tokens
162
+
163
+ Content shipped in `@ainova-systems/sync` cannot assume the project's content-directory name or package-store location. It uses these tokens:
164
+
165
+ | Token | v2 expansion |
166
+ |---|---|
167
+ | `<content-dir>` | Project content directory, usually `intelligence` |
168
+ | `<module>` | Installed sync package, usually `.intelligence/packages/@ainova-systems/sync` |
169
+ | `<manifest>` | `intelligence.yaml` |
170
+ | `<sync-cmd>` | `intelligence sync` |
171
+
172
+ Call `finalize_output_file` on every text file after transformation. It expands the tokens literally and converts CRLF to LF. A generated file containing an unexpanded token is an adapter bug.
173
+
174
+ `copy_skill_bundle` already finalizes Markdown within a skill; do not run a second custom token pass.
175
+
176
+ ## Cleanup and safety contract
177
+
178
+ Adapters regenerate output, so cleanup is part of their public contract.
179
+
180
+ 1. Delete only paths the adapter owns. Preserve sibling settings, commands, extensions, workflows and hand-authored files.
181
+ 2. Make ownership obvious in `sync_to_<name>()`; the same list is what a user removes after disabling or uninstalling the adapter.
182
+ 3. Use marker-based cleanup when generated and hand-authored files share a directory. The OpenCode adapter is the reference implementation.
183
+ 4. Use `sync_open_skill_dirs` for `.agents/skills/`; multiple adapters share it.
184
+ 5. Write only beneath the supplied `output_dir`, except for an explicitly shared standard path handled by a shared helper.
185
+ 6. Make repeated syncs idempotent.
186
+
187
+ Before an adapter runs, the engine canonicalizes and validates its configured output. It refuses the repository root, paths outside the repository, symlink escapes, the project content directory, `.intelligence/` and configured source paths. This guard does not make broad cleanup safe: an adapter must still avoid deleting unrelated files within a valid tool directory.
188
+
189
+ ## Minimal example
190
+
191
+ This example copies rules without frontmatter. A real adapter must decide how the target represents scoping and should also implement agents and skills.
192
+
193
+ ```bash
194
+ #!/bin/bash
195
+
196
+ sync_mytool_rules() {
197
+ local repo_root="$1" config_file="$2" output_dir="$3"
198
+ mkdir -p "$output_dir/rules"
199
+
200
+ while IFS= read -r src; do
201
+ [ -n "$src" ] || continue
202
+ local dir
203
+ dir="$(resolve_source_dir "$repo_root" "$src")"
204
+ [ -d "$dir" ] || continue
205
+
206
+ for file in "$dir"/*.md; do
207
+ [ -f "$file" ] || continue
208
+ strip_frontmatter "$file" > "$output_dir/rules/$(basename "$file")"
209
+ finalize_output_file "$output_dir/rules/$(basename "$file")"
210
+ done
211
+ done < <(read_yaml_list "$config_file" "rules")
212
+ }
213
+
214
+ sync_to_mytool() {
215
+ local repo_root="$1" config_file="$2" output_dir="$3"
216
+
217
+ rm -rf "$output_dir/rules"
218
+ sync_mytool_rules "$repo_root" "$config_file" "$output_dir"
219
+ }
220
+ ```
221
+
222
+ ## Testing
223
+
224
+ Use a disposable Git repository with a v2 manifest and representative always-on/scoped rules, agents, skills and bundled skill resources.
225
+
226
+ ```bash
227
+ intelligence sync mytool
228
+ ```
229
+
230
+ Verify:
231
+
232
+ - native file names, frontmatter and model/permission mappings;
233
+ - scoped and always-on routing without duplicated context;
234
+ - skill resources are present;
235
+ - no literal layout tokens remain;
236
+ - hand-authored siblings under the tool root survive;
237
+ - output paths cannot overlap sources or escape the repository;
238
+ - a second sync produces no Git diff.
239
+
240
+ For a built-in adapter, add the same assertions to the repository's smoke or lifecycle tests and run all five CLI suites before submitting the change.
241
+
242
+ ## Built-in outputs
243
+
244
+ | Adapter | Primary output |
245
+ |---|---|
246
+ | `agents` | `AGENTS.md` |
247
+ | `claude` | `.claude/rules`, `.claude/agents`, `.claude/skills` |
248
+ | `cursor` | `.cursor/rules`, `.cursor/agents`, `.cursor/skills` |
249
+ | `copilot` | `.github/instructions`, `.github/agents`, `.github/skills` |
250
+ | `codex` | `.codex/agents`, `.agents/skills` |
251
+ | `pi` | `.pi/intelligence-sync`, `.pi/extensions`, `.pi/prompts`, `.agents/skills` |
252
+ | `opencode` | `.opencode/agents`, marker-owned `.opencode/commands`, `.agents/skills` |