Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,30 @@ jobs:
grep -q "backend-developer" "$PROJ/AGENTS.md"
grep -q "backend-add-endpoint" "$PROJ/AGENTS.md"

- name: Engine-owned rule + agent reach the outputs, with layout tokens expanded
shell: bash
run: |
set -euo pipefail
cd "$PROJ"
# The engine ships intelligence-authoring (rule) + intelligence-architect
# (agent). They must arrive via sources, and every `<umbrella>` /
# `<module>` token must be gone: a literal token in an output means an
# adapter wrote a file without finalize_output_file().
grep -q "intelligence-authoring" AGENTS.md
grep -q "intelligence-architect" AGENTS.md
if [ -d .claude ]; then
test -f .claude/rules/intelligence-authoring.md
test -f .claude/agents/intelligence-architect.md
grep -q '"intelligence/\*\*"' .claude/rules/intelligence-authoring.md
fi
leaked=$(grep -rl -e '<umbrella>' -e '<module>' \
.claude .cursor .github/instructions .github/agents .github/skills \
.codex .agents .pi .opencode AGENTS.md 2>/dev/null || true)
if [ -n "$leaked" ]; then
echo "UNEXPANDED LAYOUT TOKEN shipped into generated output:"; echo "$leaked"; exit 1
fi
echo "OK: engine rule/agent present, no unexpanded tokens"

- name: Verify Claude / Cursor / Copilot / Codex outputs (if enabled)
shell: bash
run: |
Expand Down Expand Up @@ -400,29 +424,34 @@ jobs:
s=$(bash intelligence/sync/scripts/sync.sh); echo "$s" | grep -q 'IS_STATUS=ok'
test -f AGENTS.md

- name: Multi-version chain walks forward (0.3.1 → 0.4.0), idempotent
- name: Multi-version chain walks the whole registry forward, idempotent
shell: bash
run: |
set -euo pipefail
source "$STAGE_LIB"
# An upstream with a synthetic SECOND breaking migration appended.
UP=$RUNNER_TEMP/up040
# An upstream with a synthetic NEWEST breaking migration appended to
# whatever the real registry currently holds. The append is derived,
# never pinned to a literal MIGRATIONS=( ... ) line: pinning it means
# the chain silently stops being tested the moment a real migration
# is added.
UP=$RUNNER_TEMP/up990
mkdir -p "$UP"
tar --exclude='.git' -cf - . | (cd "$UP" && tar -xf -)
M="$UP/intelligence/sync/scripts/lib/migrations.sh"
printf '0.4.0\n' > "$UP/intelligence/sync/scripts/VERSION"
sed -i 's/^MIGRATIONS=( "0_3_1" )/MIGRATIONS=( "0_3_1" "0_4_0" )/' "$M"
printf '9.9.0\n' > "$UP/intelligence/sync/scripts/VERSION"
sed -i 's/^MIGRATIONS=(\(.*\))$/MIGRATIONS=(\1 "9_9_0" )/' "$M"
grep -q '"9_9_0"' "$M" || { echo "fixture failed to extend MIGRATIONS"; exit 1; }
cat >> "$M" <<'EOF'

# synthetic breaking migration for CI: structural precondition =
# absence of the marker; idempotent; transactional.
migrate_to_0_4_0() {
migrate_to_9_9_0() {
local umbrella="$1" module_name="$2"
local marker="$umbrella/$module_name/.mig040"
local marker="$umbrella/$module_name/.mig990"
[ -f "$marker" ] && return 0
IS_MIGRATED=1
: > "$marker"
echo " [migrate 0.4.0] applied"
echo " [migrate 9.9.0] applied"
}
EOF
git -C "$UP" init -q
Expand All @@ -436,13 +465,17 @@ jobs:
out=$(REPO_URL="file://$UP" bash intelligence/scripts/update.sh --yes)
echo "$out"
echo "$out" | grep -q 'IS_STATUS=migrated' || { echo "expected migrated"; exit 1; }
test -f intelligence/sync/.mig040 # 0.4.0 ran
test -f intelligence/sync/.mig990 # newest ran
! test -d intelligence/scripts # 0.3.1 ran
grep -q '^sync_version: *"0.4.0"' intelligence/config.yaml
# Idempotent: re-run is a clean no-op at 0.4.0.
# 0.7.0 ran too — the module's rules/ and agents/ are registered, once each.
test "$(grep -c '"intelligence/sync/rules"' intelligence/config.yaml)" -eq 1
test "$(grep -c '"intelligence/sync/agents"' intelligence/config.yaml)" -eq 1
grep -q '^sync_version: *"9.9.0"' intelligence/config.yaml
# Idempotent: re-run is a clean no-op, and adds no duplicate sources.
out2=$(REPO_URL="file://$UP" bash intelligence/sync/scripts/update.sh --yes)
echo "$out2" | grep -q 'IS_STATUS=ok' || { echo "expected ok on re-run"; exit 1; }
grep -q '^sync_version: *"0.4.0"' intelligence/config.yaml
grep -q '^sync_version: *"9.9.0"' intelligence/config.yaml
test "$(grep -c '"intelligence/sync/rules"' intelligence/config.yaml)" -eq 1

- name: Version-compat guard fails closed (ahead-of-engine)
shell: bash
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ Update intelligence-sync: fetch the latest engine from https://github.com/ainova

## [Unreleased]

## [0.7.0] — 2026-07-12

The engine stops being only a pipeline and starts shipping its own **authoring discipline**: a rule the model respects while it works, and an agent that owns the layer's shape. Both are upstream-owned — they evolve with the engine instead of drifting inside each project.

### Breaking

- **`config.yaml` must list the module's `rules/` and `agents/` as sources.** The engine now ships artifacts of its own beside the meta-skills, and they only reach the IDEs if `sources` points at them. `migrate_to_0_7_0` adds both entries idempotently (nothing else in `config.yaml` is read or rewritten), exactly as `0.3.1` added the module's `skills/` entry. `sync.sh` fails closed (`IS_STATUS=needs-update`) until the update flow has run — it never migrates.

Post-conditions to verify after updating:
1. `sources.rules` contains `<umbrella>/<module>/rules` and `sources.agents` contains `<umbrella>/<module>/agents` — each exactly once.
2. `<module>/rules/intelligence-authoring.md` and `<module>/agents/intelligence-architect.md` exist.
3. `config.yaml` `sync_version` is `0.7.0`.
4. After a sync, no generated file contains a literal `<umbrella>` or `<module>` token.

Project content (`rules/`, `agents/`, `skills/`, `adapters/`) is untouched. Re-running is a safe no-op.

### Added

- **`intelligence-authoring` — an engine-shipped rule carrying the authoring discipline** (`<module>/rules/`). Path-scoped to the umbrella, so it loads exactly when someone edits the layer and costs nothing otherwise. It is the judgement that sits on top of `CONVENTIONS.md`'s mechanics: which artifact type a piece of knowledge belongs to (a checklist in an agent body is a procedure — it belongs in a skill), why always-on rules must earn their place, why an agent is thin and never restates the rules it already receives, why a skill without a verification step is not a skill, and the invariant that closes the loop on the defect that produced 0.6.0 — *never state behaviour of a tool you have not verified in its documentation or source*.
- **`intelligence-architect` — an engine-shipped agent that designs and prunes the layer** (`<module>/agents/`). Decides rule vs skill vs agent, always-on vs scoped, split vs fold, and what to delete. It carries boundaries and verification only; the rule reaches it on its own, so it does not repeat it.
- **Layout tokens `<umbrella>` and `<module>` in engine-shipped artifacts.** An artifact shipped by the engine cannot hardcode the umbrella's folder name — the project chooses it (`intelligence/`, `Intelligence/`, a codename) — but a scoped rule needs `paths:` to name it. Adapters now expand both tokens on the way out through a single helper (`finalize_output_file`, which replaces `normalize_file_to_lf` at every output site), in frontmatter and body alike: `paths: ["<umbrella>/**"]` arrives as Claude's `paths:`, Cursor's `globs:` and Copilot's `applyTo:` already carrying the real folder name. CI fails the build if any generated output still contains a literal token.
- **`update.sh` owns the module's `rules/` and `agents/`** the way it already owns `docs/` and the meta-skills: staged from the fresh upstream clone, shown in the diff before the confirmation prompt, then applied. Your own `<umbrella>/rules` and `<umbrella>/agents` are never touched.

### Changed

- **INIT no longer suggests skills from a catalogue.** §3.5 listed a menu of plausible skill names (`add-entity`, `add-endpoint`, `run-tests`, …), and an agent working from a menu produces a registry that describes software in general rather than the repository in front of it — every entry costing registry budget forever, whether or not anyone invokes it. A skill must now clear four bars — **repeated** (with the instance in the repo named as evidence), **multi-step and mechanical**, **verifiable** (it ends in a check that proves it worked; if there is nothing to verify, it is not a skill), and **stable** (a procedure that only routes around a current bug belongs in a rule that records known breakage, not in a skill that makes the workaround permanent) — and bootstrap now targets **0–3 skills**, with zero stated as a legitimate answer.
- `examples/go-api-with-opencode/config.yaml` never listed the module's `skills/` as a source, so the meta-skills were missing from that fixture. All six examples now register the module's `rules/`, `agents/` and `skills/`.

## [0.6.0] — 2026-07-12

### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ intelligence-sync/
│ │ ├── VERSION # Module version (drives migrations)
│ │ ├── lib/ # common.sh, layout.sh, migrations.sh
│ │ └── adapters/ # 6 built-in + template
│ ├── rules/ # intelligence-authoring — how to author this layer
│ ├── agents/ # intelligence-architect — designs and prunes it
│ └── skills/intelligence-* # Pre-installed meta-skills
├── examples/ # config.yaml for different project types
├── docs/ # Conventions and adapter guide (source)
Expand Down
18 changes: 16 additions & 2 deletions docs/ADAPTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Source `lib/common.sh` for these utilities:

| Function | Description |
|----------|-------------|
| `normalize_file_to_lf(file)` | Convert CRLF to LF |
| `finalize_output_file(file)` | **Call this on every file you write.** Expands layout tokens (`<umbrella>`, `<module>`) and converts CRLF to LF |
| `normalize_file_to_lf(file)` | LF conversion only — for intermediate files that are not adapter output |
Comment on lines 51 to +54

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the example adapter now calls finalize_output_file after writing each rule, with a comment saying why. A doc that demonstrates the token-leaking call is exactly how the next adapter would ship a literal <umbrella> into someone's IDE. (Both the source docs/ and the vendored intelligence/sync/docs/ copy, which CI asserts are identical.)

| `lint_frontmatter(file)` | Warn about unquoted colons, leading tabs, and literal double quotes inside unquoted values (stderr) |
| `get_frontmatter_value(key, file)` | Extract YAML frontmatter value |
| `has_frontmatter(file)` | Check for `---` header |
Expand Down Expand Up @@ -98,6 +99,17 @@ Skills follow the [Agent Skills open standard](https://agentskills.io). All supp

Model names come from `get_model(config_file, ide, tier)` in `lib/common.sh`. Defaults are baked into `get_model_default()`; users override per-IDE/tier under `models:` in `config.yaml`. Sync prints a drift report when an override no longer matches the current default.

### Layout tokens (required)

The engine ships artifacts of its own — the `intelligence-authoring` rule and the `intelligence-architect` agent — and they cannot hardcode the umbrella's folder name, because the project chooses it. They write `<umbrella>` and `<module>` instead, and **every adapter must expand them by calling `finalize_output_file` on each file it writes** (it also does the LF normalization that `normalize_file_to_lf` used to do):

| Token | Expands to |
|---|---|
| `<umbrella>` | repo-relative umbrella dir (e.g. `Intelligence`) |
| `<module>` | repo-relative engine module (e.g. `Intelligence/sync`) |

Values are exported by `sync.sh` (`IS_UMBRELLA_REL`, `IS_MODULE_REL`), derived from the detected layout. Expansion covers frontmatter and body, so `paths: ["<umbrella>/**"]` reaches Claude's `paths:`, Cursor's `globs:` and Copilot's `applyTo:` carrying the project's real folder name. A file written without `finalize_output_file` ships a literal `<umbrella>` into an IDE — CI fails the build if any generated output still contains a token.

### Cleanup Contract

Every adapter MUST follow these rules to stay safe alongside others:
Expand Down Expand Up @@ -140,7 +152,9 @@ sync_to_myide() {
}
past_fm || !in_fm { print }
' "$f" > "$output_dir/rules/$(basename "$f")"
normalize_file_to_lf "$output_dir/rules/$(basename "$f")"
# Every written file goes through finalize_output_file: it expands
# the <umbrella> / <module> layout tokens and normalizes CRLF -> LF.
finalize_output_file "$output_dir/rules/$(basename "$f")"
done
done < <(read_yaml_list "$config_file" "rules")
}
Expand Down
20 changes: 18 additions & 2 deletions docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,26 @@ intelligence/ # Umbrella — name NOT hardcoded (whateve
├── adapters/ # OPTIONAL — project-owned adapters (survive updates)
│ └── myide.sh # sync_to_myide(); overrides a built-in of the same name
└── sync/ # intelligence-sync MODULE (upstream-owned)
└── INIT.md docs/ scripts/(+VERSION) skills/intelligence-*
├── INIT.md docs/ scripts/(+VERSION)
├── rules/intelligence-authoring.md # authoring discipline for this layer
├── agents/intelligence-architect.md # designs and prunes this layer
└── skills/intelligence-* # meta-skills
```

Everything project-authored lives at the umbrella level (`rules/ agents/ skills/ adapters/`); everything upstream-owned lives in the self-contained module `sync/`, updated independently via `sync/scripts/update.sh`. Additional modules (e.g. `domain/`) sit beside `sync/`. The umbrella folder name is derived at runtime as "the directory holding `config.yaml`" — never hardcoded. The `intelligence-` skill prefix is **reserved** for upstream meta-skills; project skills must not use it (the updater moves/prunes anything matching that prefix).
Everything project-authored lives at the umbrella level (`rules/ agents/ skills/ adapters/`); everything upstream-owned lives in the self-contained module `sync/`, updated independently via `sync/scripts/update.sh`. Additional modules (e.g. `domain/`) sit beside `sync/`. The umbrella folder name is derived at runtime as "the directory holding `config.yaml`" — never hardcoded. The `intelligence-` prefix is **reserved** for upstream artifacts; project rules, agents and skills must not use it (the updater prunes what matches it).

The module ships three kinds of artifact, and `config.yaml` must list all three under `sources` for them to reach the IDEs — `<umbrella>/sync/rules`, `<umbrella>/sync/agents`, `<umbrella>/sync/skills`. INIT emits those entries on bootstrap; the `0.7.0` migration adds them to existing projects.

### Layout tokens in engine-shipped artifacts

An artifact shipped *by the engine* cannot write the umbrella's name down — the project chooses it (`intelligence/`, `Intelligence/`, a codename). So engine artifacts spell it with tokens, and every adapter expands them on the way out (`finalize_output_file` in `lib/common.sh`):

| Token | Expands to | Example |
|---|---|---|
| `<umbrella>` | repo-relative umbrella dir | `Intelligence` |
| `<module>` | repo-relative engine module | `Intelligence/sync` |

Expansion covers frontmatter and body alike, so `paths: ["<umbrella>/**"]` reaches Claude's `paths:`, Cursor's `globs:` and Copilot's `applyTo:` already carrying the project's real folder name. Project-authored artifacts may use the tokens too, but they have no reason to — they can simply name their own folders.

A custom adapter belongs in the umbrella's `adapters/`, never in the module's `sync/scripts/adapters/` — the module is replaced wholesale on every update, so an adapter written there disappears at the next one. See `docs/ADAPTERS.md`.

Expand Down
4 changes: 3 additions & 1 deletion examples/dotnet-api-with-react-frontend/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
project:
name: my-platform

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "backend/intelligence/rules"
- "frontend/intelligence/rules"
- "intelligence/sync/rules"
agents:
- "intelligence/agents"
- "backend/intelligence/agents"
- "frontend/intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"
Expand Down
5 changes: 4 additions & 1 deletion examples/go-api-with-opencode/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
project:
name: my-api

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "intelligence/sync/rules"
agents:
- "intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"

targets:
agents:
Expand Down
4 changes: 3 additions & 1 deletion examples/go-api-with-pi-and-codex/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
project:
name: my-api

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "intelligence/sync/rules"
agents:
- "intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"
Expand Down
4 changes: 3 additions & 1 deletion examples/go-api/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
project:
name: my-api

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "intelligence/sync/rules"
agents:
- "intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"
Expand Down
4 changes: 3 additions & 1 deletion examples/platform-with-submodules/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
project:
name: my-platform

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "api/intelligence/rules"
- "web/intelligence/rules"
- "deploy/intelligence/rules"
- "intelligence/sync/rules"
agents:
- "intelligence/agents"
- "api/intelligence/agents"
- "web/intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"
Expand Down
4 changes: 3 additions & 1 deletion examples/with-remote-skills/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
project:
name: my-app

sync_version: "0.6.0"
sync_version: "0.7.0"

sources:
rules:
- "intelligence/rules"
- "intelligence/sync/rules"
# Shared rules from a central repo, pinned to a tag:
- "git+https://github.com/org/shared-intel.git@v1.2.0#rules"
agents:
- "intelligence/agents"
- "intelligence/sync/agents"
skills:
- "intelligence/skills"
- "intelligence/sync/skills"
Expand Down
Loading
Loading