@ainova-systems/intelligence 0.23.0 → 0.25.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.
@@ -8,6 +8,74 @@ warn() { echo " ✗ $*"; problems=$((problems + 1)); }
8
8
  note() { echo " ! $*"; }
9
9
  ok() { echo " ✓ $*"; }
10
10
 
11
+ # claude_md_excluded <path> <settings>... - whether an entry of
12
+ # claudeMdExcludes in one of the settings files matches <path>. Claude Code
13
+ # matches those globs against absolute paths; this reads `**`, `*` and `?` and
14
+ # takes every other character literally. A Windows path is tried in both the
15
+ # Git Bash spelling and the drive spelling (`/d/x`, `D:/x`).
16
+ claude_md_excluded() {
17
+ local f alt=""
18
+ if command -v cygpath >/dev/null 2>&1; then alt="$(cygpath -m "$1")"; fi
19
+ for f in "${@:2}"; do
20
+ [ -f "$f" ] || continue
21
+ CMX_PATH="$1" CMX_ALT="$alt" awk '
22
+ function glob_re(g, i, c, out) {
23
+ out = "^"
24
+ for (i = 1; i <= length(g); i++) {
25
+ c = substr(g, i, 1)
26
+ if (c == "*" && substr(g, i + 1, 1) == "*") {
27
+ i++
28
+ if (substr(g, i + 1, 1) == "/") { i++; out = out "(.*/)?" }
29
+ else out = out ".*"
30
+ }
31
+ else if (c == "*") out = out "[^/]*"
32
+ else if (c == "?") out = out "[^/]"
33
+ else if (index("\\^$.|+()[]{}", c)) out = out "\\" c
34
+ else out = out c
35
+ }
36
+ return out "$"
37
+ }
38
+ { buf = buf $0 "\n" }
39
+ END {
40
+ p = index(buf, "\"claudeMdExcludes\"")
41
+ if (!p) exit 1
42
+ s = substr(buf, p + 18)
43
+ if (!sub(/^[ \t\r\n]*:[ \t\r\n]*\[/, "", s)) exit 1
44
+ while (1) {
45
+ sub(/^[ \t\r\n,]*/, "", s)
46
+ if (substr(s, 1, 1) != "\"") exit 1
47
+ v = ""
48
+ for (i = 2; i <= length(s); i++) {
49
+ c = substr(s, i, 1)
50
+ if (c == "\\") { i++; v = v substr(s, i, 1); continue }
51
+ if (c == "\"") break
52
+ v = v c
53
+ }
54
+ s = substr(s, i + 1)
55
+ re = glob_re(v)
56
+ if (ENVIRON["CMX_PATH"] ~ re) exit 0
57
+ if (ENVIRON["CMX_ALT"] != "" && ENVIRON["CMX_ALT"] ~ re) exit 0
58
+ }
59
+ }
60
+ ' "$f" && return 0
61
+ done
62
+ return 1
63
+ }
64
+
65
+ # claude_md_above <dir> - whether <dir> or a directory above it holds a
66
+ # CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md, any of which keeps Claude
67
+ # Code from reading AGENTS.md. The user's own ~/.claude/CLAUDE.md does not.
68
+ claude_md_above() {
69
+ local d="$1" up
70
+ while :; do
71
+ [ ! -e "$d/CLAUDE.md" ] && [ ! -e "$d/CLAUDE.local.md" ] || return 0
72
+ if [ -e "$d/.claude/CLAUDE.md" ] && ! [ "$d" -ef "${HOME:-}" ]; then return 0; fi
73
+ up="${d%/*}"
74
+ [ -n "$d" ] && [ "$up" != "$d" ] || return 1
75
+ d="$up"
76
+ done
77
+ }
78
+
11
79
  echo "environment:"
12
80
  command -v git >/dev/null 2>&1 && ok "git $(git --version | awk '{print $3}')" || warn "git not found"
13
81
  command -v awk >/dev/null 2>&1 && ok "awk present" || warn "awk not found"
@@ -144,6 +212,7 @@ content_dir="$(manifest_intelligence_dir "$manifest")"
144
212
  # Ownership across enabled adapters: two adapters claiming one owned path prune
145
213
  # each other silently, and sync reports success for both.
146
214
  adapter_claims_reset
215
+ claude_out="" agents_out="" disabled_owned=""
147
216
  while IFS= read -r target; do
148
217
  [ -n "$target" ] || continue
149
218
  output="$(get_target_output "$manifest" "$target")"
@@ -161,6 +230,14 @@ while IFS= read -r target; do
161
230
  if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
162
231
  adapter_claims_add_records "$target" "$records" \
163
232
  || warn "$IS_ADAPTER_CLAIM_CONFLICT"
233
+ case "$target" in
234
+ claude) claude_out="$output" ;;
235
+ agents) agents_out="$output" ;;
236
+ esac
237
+ else
238
+ while IFS=$'\t' read -r kind value; do
239
+ [ "$kind" != owned ] || disabled_owned+="$target"$'\t'"$value"$'\n'
240
+ done <<< "$records"
164
241
  fi
165
242
  while IFS=$'\t' read -r kind value; do
166
243
  if [ "$kind" = "requires" ] \
@@ -204,6 +281,52 @@ while IFS= read -r target; do
204
281
  ok "adapter $target contract v1"
205
282
  done < <(target_names "$manifest")
206
283
 
284
+ # Disabling an adapter keeps its output for a cleanup the owner approves. An
285
+ # owned path an enabled adapter also claims is that adapter's output now. The
286
+ # empty line the here-string ends with reports the last adapter.
287
+ left_target="" left_paths=""
288
+ while IFS=$'\t' read -r target value; do
289
+ if [ "$target" != "$left_target" ] && [ -n "$left_paths" ]; then
290
+ note "adapter '$left_target' is disabled, and its output is still here: ${left_paths#, }. The intelligence-manage-adapters skill reviews it and removes what you approve"
291
+ left_paths=""
292
+ fi
293
+ left_target="$target"
294
+ [ -n "$target" ] || continue
295
+ [ -e "$IP_ROOT/$value" ] || [ -L "$IP_ROOT/$value" ] || continue
296
+ adapter_contract_rel_path "$value"
297
+ claimed=0
298
+ while IFS=$'\t' read -r _ _ path; do
299
+ [ -n "$path" ] || continue
300
+ case "$IS_ADAPTER_REL_PATH/" in "$path"/*) claimed=1 ;; esac
301
+ case "$path/" in "$IS_ADAPTER_REL_PATH"/*) claimed=1 ;; esac
302
+ done <<< "$IS_ADAPTER_CLAIMS"
303
+ [ "$claimed" = 1 ] || left_paths="$left_paths, $IS_ADAPTER_REL_PATH"
304
+ done <<< "$disabled_owned"
305
+
306
+ # Claude Code 2.1.277 and later reads AGENTS.md as project instructions when no
307
+ # CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md exists in the project or above
308
+ # it, and keeps loading .claude/rules/ beside it (code.claude.com/docs/en/memory).
309
+ # With both targets enabled every always-on rule then loads twice. The contract
310
+ # preserves settings.json, so the exclude is the owner's to add.
311
+ if [ -n "$claude_out" ] && [ -n "$agents_out" ]; then
312
+ agents_output_path_var "$agents_out"
313
+ adapter_contract_rel_path "$IS_AGENTS_OUTPUT_PATH"
314
+ agents_md="$IS_ADAPTER_REL_PATH"
315
+ adapter_contract_rel_path "$claude_out"
316
+ claude_out="$IS_ADAPTER_REL_PATH"
317
+ case "/$agents_md" in
318
+ */.agents/*) ;;
319
+ */AGENTS.md)
320
+ if ! claude_md_above "$IP_ROOT" && [ ! -e "$IP_ROOT/$claude_out/CLAUDE.md" ] \
321
+ && ! claude_md_excluded "$IP_ROOT/$agents_md" \
322
+ "$IP_ROOT/$claude_out/settings.json" "$IP_ROOT/$claude_out/settings.local.json"; then
323
+ note "Claude Code 2.1.277 and later reads $agents_md when there is no CLAUDE.md, $claude_out/CLAUDE.md or CLAUDE.local.md, so the always-on rules load twice: from $claude_out/rules/ and from $agents_md. Sync never edits settings; put this key in $claude_out/settings.json:"
324
+ printf ' { "claudeMdExcludes": ["**/%s"] }\n' "$agents_md"
325
+ fi
326
+ ;;
327
+ esac
328
+ fi
329
+
207
330
  # A local rule, agent or skill is one these patterns name (decision 0021): the
208
331
  # engine keeps it out of every committed output, and without the pattern Git
209
332
  # would still offer to commit the file itself.
package/engine/ENGINE_SHA CHANGED
@@ -1 +1 @@
1
- 79201cedae74ec96e67c3aa94e267f6b1e226c23
1
+ faf0b3ae4ad1b07f08463caf9a332c1eb3a6dcc9
package/engine/VERSION CHANGED
@@ -1 +1 @@
1
- 0.23.0
1
+ 0.25.0
@@ -34,6 +34,17 @@ agents_md_module_rel() {
34
34
  printf '%s' "${IS_MODULE_REL:-.intelligence/packages/@ainova-systems/sync}"
35
35
  }
36
36
 
37
+ # agents_md_link_var <repo_root> <path>: the link target AGENTS.md gives an
38
+ # artifact in IS_REPO_REL, "" for its bare name. The package store is ignored,
39
+ # so a link into .intelligence/ is dead in every clone before a sync and on the
40
+ # forge: store content is listed by name, like content outside the root.
41
+ agents_md_link_var() {
42
+ repo_rel_link_var "$1" "$2"
43
+ case "$IS_REPO_REL" in
44
+ .intelligence/*) IS_REPO_REL="" ;;
45
+ esac
46
+ }
47
+
37
48
  # Append the static header block from config.yaml (or a fallback).
38
49
  agents_md_append_header() {
39
50
  local output="$1"
@@ -73,7 +84,7 @@ agents_md_append_agents_table() {
73
84
  while IFS=$'\x1f' read -r path tier access desc; do
74
85
  name="${names[i]}"
75
86
  i=$((i + 1))
76
- repo_rel_link_var "$repo_root" "$path"
87
+ agents_md_link_var "$repo_root" "$path"
77
88
  if [ -n "$IS_REPO_REL" ]; then
78
89
  rows+="| [$name]($IS_REPO_REL) | ${tier:--} | ${access:--} | ${desc:--} |"$'\n'
79
90
  else
@@ -118,7 +129,7 @@ agents_md_append_skills_table() {
118
129
  while IFS=$'\x1f' read -r path desc; do
119
130
  name="${names[i]}"
120
131
  i=$((i + 1))
121
- repo_rel_link_var "$repo_root" "$path"
132
+ agents_md_link_var "$repo_root" "$path"
122
133
  if [ -n "$IS_REPO_REL" ]; then
123
134
  rows+="| [$name]($IS_REPO_REL) | ${desc:--} |"$'\n'
124
135
  else
@@ -162,7 +173,7 @@ agents_md_append_rules_list() {
162
173
  else
163
174
  global_rule_files+=("$path")
164
175
  fi
165
- repo_rel_link_var "$repo_root" "$path"
176
+ agents_md_link_var "$repo_root" "$path"
166
177
  if [ -n "$IS_REPO_REL" ]; then
167
178
  lines+="- [$name]($IS_REPO_REL) ($scope)"$'\n'
168
179
  else
@@ -3,8 +3,7 @@
3
3
  # Transforms source prompts to Codex format.
4
4
  #
5
5
  # Codex reads AGENTS.md natively for project context — the `agents` adapter
6
- # inlines always-on rules into AGENTS.md, so this adapter only emits skills
7
- # and subagent definitions.
6
+ # inlines shared always-on rules into AGENTS.md.
8
7
  #
9
8
  # Rules: an always-on rule AGENTS.md does not carry — a local one, or one whose
10
9
  # targets leave `agents` out — goes into AGENTS.override.md beside it
@@ -12,6 +11,8 @@
12
11
  # directory, so it holds the whole AGENTS.md plus those rules. The contract
13
12
  # ignores it, and it exists only while such a rule does; one Intelligence did
14
13
  # not generate is never touched, and refuses a render that needs the path.
14
+ # Scoped rules get compact pattern-to-source links in that same override.
15
+ # This is on-demand reading guidance, not a native glob-triggered loader.
15
16
  #
16
17
  # Skills: copy skill directories in full to .agents/skills/{name}/ (Codex
17
18
  # reads from $REPO_ROOT/.agents/skills exclusively per official docs)
@@ -43,49 +44,79 @@ adapter_contract_codex() {
43
44
  CODEX_OVERRIDE_MARKER="<!-- Generated by intelligence-sync. Do not edit manually. -->"
44
45
 
45
46
  # sync_codex_override <repo_root> <config_file> — write AGENTS.override.md
46
- # while an always-on rule AGENTS.md does not carry is for Codex, and remove a
47
- # generated one otherwise.
47
+ # while Codex needs extra always-on rules or scoped-rule routing, and remove
48
+ # a generated one otherwise.
48
49
  sync_codex_override() {
49
50
  local repo_root="$1" config_file="$2" override="$1/AGENTS.override.md" first="" agents_file i
50
- local -a files=() names=()
51
+ local -a files=() names=() scoped=() scoped_names=()
51
52
  source_artifacts_var "$repo_root" "$config_file" rules codex "AGENTS.override.md"
52
53
  for ((i = 0; i < ${#IS_SOURCE_FILES[@]}; i++)); do
53
- [ "${IS_SOURCE_SCOPED[i]}" = 0 ] && [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ] || continue
54
- files+=("${IS_SOURCE_FILES[i]}")
54
+ if [ "${IS_SOURCE_SCOPED[i]}" = 1 ]; then
55
+ scoped+=("${IS_SOURCE_FILES[i]}")
56
+ scoped_names+=("${IS_SOURCE_NAMES[i]}")
57
+ elif [ "${IS_SOURCE_IN_AGENTS[i]}" = 0 ]; then
58
+ files+=("${IS_SOURCE_FILES[i]}")
59
+ else
60
+ continue
61
+ fi
55
62
  names+=("${IS_SOURCE_NAMES[i]}")
56
63
  done
57
64
  if [ -f "$override" ]; then
58
65
  IFS= read -r first < "$override" || true
59
66
  if [ "${first%$'\r'}" != "$CODEX_OVERRIDE_MARKER" ]; then
60
- [ "${#files[@]}" -gt 0 ] || return 0
61
- echo "ERROR: AGENTS.override.md was not generated by intelligence-sync, but ${names[*]} need it: always-on rules for Codex that AGENTS.md does not carry go there. Move its guidance into a rule, remove it, then sync." >&2
67
+ [ "${#names[@]}" -gt 0 ] || return 0
68
+ echo "ERROR: AGENTS.override.md was not generated by intelligence-sync, but ${names[*]} need it: additional Codex rules and scoped-rule routing go there. Move its guidance into a rule, remove it, then sync." >&2
62
69
  return 1
63
70
  fi
64
71
  fi
65
- if [ "${#files[@]}" -eq 0 ]; then
72
+ if [ "${#names[@]}" -eq 0 ]; then
66
73
  rm -f "$override"
67
74
  return 0
68
75
  fi
69
76
  target_output_var "$config_file" "agents"
70
77
  agents_output_path_var "${IS_TGT_OUTPUT:-.agents}"
71
78
  agents_file="$repo_root/$IS_AGENTS_OUTPUT_PATH"
79
+ local routes="" rows path patterns link
80
+ if [ "${#scoped[@]}" -gt 0 ]; then
81
+ rows="$(frontmatter_index 'paths[]' "${scoped[@]}")" || return 1
82
+ i=0
83
+ while IFS=$'\x1f' read -r path patterns; do
84
+ repo_rel_link_var "$repo_root" "$path"
85
+ # The override is ignored: external sources may use their real path.
86
+ link="${IS_REPO_REL:-$path}"
87
+ routes+="- \`${patterns:-[]}\` → [${scoped_names[i]}](<$link>)"$'\n'
88
+ i=$((i + 1))
89
+ done <<< "$rows"
90
+ fi
91
+ # Only the Codex section is finalized: AGENTS.md already is, and a second
92
+ # pass would expand the tokens its rules escaped.
93
+ local section=""
72
94
  {
73
- if [ -f "$agents_file" ]; then
74
- cat "$agents_file"
75
- else
76
- printf '%s\n\n' "$CODEX_OVERRIDE_MARKER"
77
- fi
78
95
  echo "---"
79
96
  echo ""
80
97
  echo "## Codex Project Context"
81
98
  echo ""
82
- echo "<!-- Always-on rules AGENTS.md does not carry. Codex reads this file instead of AGENTS.md. -->"
99
+ echo "<!-- Codex reads this file instead of AGENTS.md. -->"
83
100
  echo ""
84
- emit_rule_bodies "${files[@]}"
101
+ if [ -n "$routes" ]; then
102
+ echo "Before working on files matching these repo-relative patterns, read and follow the linked rules. Apply each only within its scope."
103
+ echo ""
104
+ printf '%s\n' "$routes"
105
+ fi
106
+ [ "${#files[@]}" -eq 0 ] || emit_rule_bodies "${files[@]}"
85
107
  } > "$override"
86
108
  finalize_output_file "$override"
109
+ IFS= read -r -d '' section < "$override" || true
110
+ {
111
+ if [ -f "$agents_file" ]; then
112
+ cat "$agents_file"
113
+ else
114
+ printf '%s\n\n' "$CODEX_OVERRIDE_MARKER"
115
+ fi
116
+ printf '%s' "$section"
117
+ } > "$override"
87
118
  for ((i = 0; i < ${#names[@]}; i++)); do
88
- echo " rule: ${names[i]} (always-on, AGENTS.override.md)"
119
+ echo " rule: ${names[i]} (AGENTS.override.md)"
89
120
  done
90
121
  echo " -> AGENTS.override.md"
91
122
  }
@@ -39,8 +39,9 @@ adapter_contract_pi() {
39
39
 
40
40
  # Fork-free TS string escape: sets IS_PI_TS_ESCAPED.
41
41
  pi_ts_escape_var() {
42
- local s="$1"
43
- s="${s//\\/\\\\}"
42
+ local s
43
+ token_keep_var "$1"
44
+ s="${IS_TOKEN_KEPT//\\/\\\\}"
44
45
  s="${s//\"/\\\"}"
45
46
  s="${s//$'\r'/}"
46
47
  s="${s//$'\n'/ }"
@@ -109,7 +110,7 @@ pi_rule_body_rows() {
109
110
  /^---$/ && !past_fm { in_fm = !in_fm; if (!in_fm) past_fm = 1; next }
110
111
  !past_fm { next }
111
112
  {
112
- line = is_repl($0, "\\", "\\\\")
113
+ line = is_repl(fin_keep($0), "\\", "\\\\")
113
114
  line = is_repl(line, "\"", "\\\"")
114
115
  line = is_repl(line, "\t", "\\t")
115
116
  B[++n] = line
@@ -23,6 +23,9 @@ normalize_file_to_lf() {
23
23
  # <content-dir> -> the repo-relative project content dir (e.g. `Intelligence`)
24
24
  # <module> -> the repo-relative sync package store path
25
25
  #
26
+ # A backslash in front keeps a token literal, and the source lint warns about a
27
+ # token in a project source that reads as ordinary text (_IS_AWK_LINT).
28
+ #
26
29
  # Values come from IS_CONTENT_REL / IS_MODULE_REL, which the CLI derives from
27
30
  # the detected layout (never hardcoded) and exports before any adapter runs.
28
31
  # Expansion happens in EVERY generated file, frontmatter and body alike, so a
@@ -50,11 +53,24 @@ IS_AWK_LIB='
50
53
  }
51
54
  return out s
52
55
  }
56
+ # A backslash keeps a token as written: `\<module>` renders as `<module>`.
57
+ # The escaped token waits on a control character while the others expand.
58
+ # A writer that doubles backslashes for YAML or TOML calls fin_keep (or
59
+ # token_keep_var) first: once escaped, `\\<module>` no longer reads as one.
60
+ function fin_keep(s) {
61
+ if (!index(s, "\\<")) return s
62
+ s = is_repl(s, "\\<sync-cmd>", "\034sync-cmd>")
63
+ s = is_repl(s, "\\<manifest>", "\034manifest>")
64
+ s = is_repl(s, "\\<module>", "\034module>")
65
+ return is_repl(s, "\\<content-dir>", "\034content-dir>")
66
+ }
53
67
  function fin_line(s) {
68
+ s = fin_keep(s)
54
69
  s = is_repl(s, "<sync-cmd>", FIN_SC)
55
70
  s = is_repl(s, "<manifest>", FIN_MF)
56
71
  s = is_repl(s, "<module>", FIN_MOD)
57
72
  s = is_repl(s, "<content-dir>", FIN_CONTENT)
73
+ if (index(s, "\034")) s = is_repl(s, "\034", "<")
58
74
  return s
59
75
  }
60
76
  function fm_value_strip(val, n, first, last) {
@@ -217,7 +233,9 @@ source_name_map_var() {
217
233
  # unit separator — never present in frontmatter values). Value semantics are
218
234
  # get_frontmatter_value's exactly: first frontmatter block only, first key
219
235
  # occurrence wins, first-colon split, symmetric quote strip. The special key
220
- # `paths#` yields the has_paths count instead of a value. A file without
236
+ # `paths#` yields the has_paths count; `paths[]` folds the first paths list
237
+ # into one display value, preserving YAML quotes and inline list syntax.
238
+ # A file without
221
239
  # frontmatter (or an empty file) still gets a row, with empty values.
222
240
  frontmatter_index() {
223
241
  local keys="$1"
@@ -235,7 +253,7 @@ frontmatter_index() {
235
253
  store()
236
254
  cur = FILENAME
237
255
  for (ri = 1; ri <= nk; ri++) { V[ri] = (K[ri] == "paths#") ? 0 : ""; delete SEEN[ri] }
238
- in_fm = 0; fm_done = 0
256
+ in_fm = 0; fm_done = 0; paths_list = 0
239
257
  }
240
258
  { sub(/\r$/, "") }
241
259
  FNR == 1 && $0 == "---" { in_fm = 1; next }
@@ -243,12 +261,29 @@ frontmatter_index() {
243
261
  in_fm && !fm_done && $0 == "---" { fm_done = 1; next }
244
262
  fm_done || !in_fm { next }
245
263
  {
264
+ if (paths_list && /^[[:space:]]*#/) next
265
+ if (paths_list && (/^[[:space:]]+/ || /^-[[:space:]]+/ || /^\]/)) {
266
+ item = $0
267
+ sub(/^[[:space:]]*/, "", item)
268
+ sub(/^-[[:space:]]+/, "", item)
269
+ if (item != "" && item !~ /^#/) {
270
+ for (ki = 1; ki <= nk; ki++)
271
+ if (K[ki] == "paths[]") V[ki] = V[ki] (V[ki] == "" ? "" : " ") item
272
+ }
273
+ next
274
+ }
275
+ if ($0 !~ /^[[:space:]]*$/) paths_list = 0
246
276
  idx = index($0, ":")
247
277
  if (idx == 0) next
248
278
  k = substr($0, 1, idx - 1)
249
279
  for (ki = 1; ki <= nk; ki++) {
250
280
  if (K[ki] == "paths#") {
251
281
  if (k == "paths") V[ki]++
282
+ } else if (K[ki] == "paths[]" && k == "paths" && !(ki in SEEN)) {
283
+ SEEN[ki] = 1
284
+ V[ki] = substr($0, idx + 1)
285
+ sub(/^[[:space:]]+/, "", V[ki])
286
+ paths_list = 1
252
287
  } else if (k == K[ki] && !(ki in SEEN)) {
253
288
  SEEN[ki] = 1
254
289
  V[ki] = fm_value_strip(substr($0, idx + 1))
@@ -269,12 +304,28 @@ frontmatter_index() {
269
304
  ' "$@"
270
305
  }
271
306
 
307
+ # token_keep_var <s> — fin_keep for the shell escapers below, whose output
308
+ # fin_line finalizes later: an escaped layout token gives up its backslash
309
+ # before they double backslashes. Sets IS_TOKEN_KEPT.
310
+ token_keep_var() {
311
+ local s="$1" fs=$'\034' t
312
+ case "$s" in
313
+ *'\<'*)
314
+ for t in sync-cmd manifest module content-dir; do
315
+ s="${s//\\<$t>/$fs$t>}"
316
+ done
317
+ ;;
318
+ esac
319
+ IS_TOKEN_KEPT="$s"
320
+ }
321
+
272
322
  # Escape a string for safe interpolation into a TOML basic string ("..").
273
323
  # Backslash and double-quote are escaped; control chars stripped.
274
324
  # Fork-free form: sets IS_TOML_ESCAPED; the printing form wraps it.
275
325
  toml_escape_var() {
276
- local s="$1"
277
- s="${s//\\/\\\\}"
326
+ local s
327
+ token_keep_var "$1"
328
+ s="${IS_TOKEN_KEPT//\\/\\\\}"
278
329
  s="${s//\"/\\\"}"
279
330
  # Strip any literal newline / carriage return — TOML basic strings
280
331
  # do not allow them; multi-line content belongs in `"""..."""`.
@@ -291,8 +342,9 @@ toml_escape() {
291
342
  # Escape a string for safe interpolation into a YAML double-quoted scalar.
292
343
  # Fork-free form: sets IS_YAML_ESCAPED; the printing form wraps it.
293
344
  yaml_dq_escape_var() {
294
- local s="$1"
295
- s="${s//\\/\\\\}"
345
+ local s
346
+ token_keep_var "$1"
347
+ s="${IS_TOKEN_KEPT//\\/\\\\}"
296
348
  s="${s//\"/\\\"}"
297
349
  s="${s//$'\n'/ }"
298
350
  s="${s//$'\r'/}"
@@ -450,7 +502,7 @@ emit_wrapped_bodies() {
450
502
  else if (!past_fm) next
451
503
  line = $0
452
504
  if (ESC[cur] == "toml") {
453
- line = is_repl(line, "\\", "\\\\")
505
+ line = is_repl(fin_keep(line), "\\", "\\\\")
454
506
  line = is_repl(line, "\"\"\"", "\"\"\\\"")
455
507
  }
456
508
  body[++body_n] = line
@@ -495,13 +547,15 @@ emit_rule_bodies() {
495
547
  # When wrapping an unquoted value, literal `\` and `"` inside it are escaped
496
548
  # (`\\`, `\"`) so an inner quote — e.g. `Use as a quick "what do we have" view`
497
549
  # — cannot prematurely terminate the generated double-quoted scalar. Values the
498
- # author already wrapped (in `"` or `'`) pass through untouched.
550
+ # author already wrapped (in `"` or `'`) pass through untouched. An escaped
551
+ # layout token is resolved first (fin_keep), so <dst> still needs
552
+ # finalize_output_file, as every emitted file does.
499
553
  #
500
554
  # Usage: copy_md_with_quoted_frontmatter "src.md" "dst.md"
501
555
  copy_md_with_quoted_frontmatter() {
502
556
  local src="$1"
503
557
  local dst="$2"
504
- awk '
558
+ awk "$IS_AWK_LIB"'
505
559
  function yamlq(s, out, i, c) {
506
560
  out = ""
507
561
  for (i = 1; i <= length(s); i++) {
@@ -531,7 +585,7 @@ copy_md_with_quoted_frontmatter() {
531
585
  first = substr(val, 1, 1)
532
586
  last = substr(val, length(val), 1)
533
587
  if ((first == "\"" && last == "\"") || (first == "\047" && last == "\047")) { print; next }
534
- print key ": \"" yamlq(val) "\""
588
+ print key ": \"" yamlq(fin_keep(val)) "\""
535
589
  next
536
590
  }
537
591
  state == "after" { print }
@@ -813,7 +867,7 @@ _skill_bundles_flush() {
813
867
  first = substr(val, 1, 1)
814
868
  last = substr(val, length(val), 1)
815
869
  if (!((first == "\"" && last == "\"") || (first == "\047" && last == "\047"))) {
816
- $0 = key ": \"" yamlq(val) "\""
870
+ $0 = key ": \"" yamlq(fin_keep(val)) "\""
817
871
  }
818
872
  }
819
873
  }
@@ -1070,9 +1124,41 @@ _IS_AWK_LINT='
1070
1124
  LEVEL[LV[li]] = 1
1071
1125
  allowed = allowed (li > 1 ? ", " : "") LV[li]
1072
1126
  }
1127
+ tok_root = ENVIRON["IS_TOK_ROOT"]
1128
+ split("<content-dir> <module> <manifest> <sync-cmd>", TOK, " ")
1129
+ TOKV[1] = FIN_CONTENT; TOKV[2] = FIN_MOD; TOKV[3] = FIN_MF; TOKV[4] = FIN_SC
1130
+ }
1131
+ # A layout token that cannot mean what it renders as: joined to a word
1132
+ # or a path around it, or an XML element closed on the same line. A
1133
+ # module-name placeholder `<module>_update_N` rendered as the package
1134
+ # path. Packages use tokens by design, so only project sources (under
1135
+ # the root, outside its package store) are read, and an escaped token
1136
+ # (`\<module>`) is literal.
1137
+ function tok_check(l, k, t, n, at, p, b, a) {
1138
+ for (k = 1; k <= 4; k++) {
1139
+ t = TOK[k]; n = length(t); at = 0
1140
+ while ((p = index(substr(l, at + 1), t)) > 0) {
1141
+ at += p
1142
+ b = (at > 1) ? substr(l, at - 1, 1) : ""
1143
+ a = substr(l, at + n, 2)
1144
+ at += n - 1
1145
+ if (b == "\\") continue
1146
+ if (b ~ /[[:alnum:]_]/ || b == "/" || a ~ /^([[:alnum:]_-]|[.][[:alnum:]_])/ \
1147
+ || index(l, "</" substr(t, 2))) {
1148
+ printf "WARNING: %s:%d %s is part of the text around it, but sync renders this layout token as %s; write \\%s to keep it as written\n", \
1149
+ substr(FILENAME, length(tok_root) + 2), FNR, t, TOKV[k], t > "/dev/stderr"
1150
+ break
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ FNR == 1 {
1156
+ in_fm = 0; done = 0; effort_seen = 0
1157
+ tok_own = tok_root != "" && index(FILENAME, tok_root "/") == 1 \
1158
+ && index(FILENAME, tok_root "/.intelligence/") != 1
1073
1159
  }
1074
- FNR == 1 { in_fm = 0; done = 0; effort_seen = 0 }
1075
1160
  { sub(/\r$/, "") }
1161
+ tok_own && index($0, ">") { tok_check($0) }
1076
1162
  done { next }
1077
1163
  FNR == 1 && $0 != "---" { done = 1; next }
1078
1164
  FNR == 1 { in_fm = 1; next }
@@ -2423,7 +2509,7 @@ source_catalog_build() {
2423
2509
  # One pass reads what routing needs from every source's frontmatter
2424
2510
  # and, for the engine, lints it. Captured, not streamed: a short answer
2425
2511
  # must stop the run, not route the files it lost as always-on.
2426
- rows="$(source_frontmatter_scan "$lint" "${files[@]}")" || {
2512
+ rows="$(source_frontmatter_scan "${lint:+$repo_root}" "${files[@]}")" || {
2427
2513
  echo "ERROR: cannot read the frontmatter of the sources — refusing to render a partial list." >&2
2428
2514
  exit 1
2429
2515
  }
@@ -2447,20 +2533,22 @@ source_catalog_build() {
2447
2533
  [ "${IS_SOURCE_FILES_MEMO:-0}" != 1 ] || IS_CAT_KEY="$key"
2448
2534
  }
2449
2535
 
2450
- # source_frontmatter_scan <lint> <file>... — one row per file in argument
2536
+ # source_frontmatter_scan <lint-root> <file>... - one row per file in argument
2451
2537
  # order, \x1f-separated: the path; its `paths:` line count, read with
2452
2538
  # frontmatter_index's `paths#` semantics; then for `targets:` and for
2453
2539
  # `exclude_targets:` whether it is set and its ids, space-separated. Both take
2454
2540
  # the list syntax `paths:` takes — a block list or a flow list `[a, b]`, items
2455
2541
  # optionally quoted — and a bare value is a one-item list. The first
2456
2542
  # occurrence of a key wins, and a key with no value at all is not set
2457
- # (`targets: []` is: it is the empty list). With <lint> = lint the same pass
2458
- # runs lint_frontmatter_files over every file, its warnings on stderr.
2543
+ # (`targets: []` is: it is the empty list). With <lint-root>, the repository
2544
+ # root, the same pass runs lint_frontmatter_files over every file and the
2545
+ # layout-token check over the project's own; warnings go to stderr.
2459
2546
  source_frontmatter_scan() {
2460
- local do_lint=0
2461
- [ "$1" != lint ] || do_lint=1
2547
+ local do_lint=0 root="$1"
2548
+ [ -z "$root" ] || do_lint=1
2462
2549
  shift
2463
- awk -v levels="$IS_EFFORT_LEVELS" -v do_lint="$do_lint" "$IS_AWK_LIB"'
2550
+ is_fin_awk_vars
2551
+ IS_TOK_ROOT="$root" awk -v levels="$IS_EFFORT_LEVELS" -v do_lint="$do_lint" "${IS_FIN_V[@]}" "$IS_AWK_LIB"'
2464
2552
  BEGIN { SX_US = sprintf("%c", 31) }
2465
2553
  function sx_items(s, k, parts, m, j, v, q) {
2466
2554
  m = split(s, parts, ",")
@@ -2767,7 +2855,7 @@ source_catalog_check_local() {
2767
2855
  # A project adapter's channels are its own business: it has one.
2768
2856
  builtin_has_channel() {
2769
2857
  case "$1:$2" in
2770
- opencode:always|opencode:scoped|codex:scoped) return 1 ;;
2858
+ opencode:always|opencode:scoped) return 1 ;;
2771
2859
  esac
2772
2860
  return 0
2773
2861
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainova-systems/intelligence",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
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"
@@ -42,7 +42,7 @@ To stop syncing a target:
42
42
  intelligence adapter disable mytool
43
43
  ```
44
44
 
45
- Disabling changes only target state. Generated output is deliberately kept so disabling is reversible; the adapter contract identifies its paths for review. 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.
45
+ Disabling changes only target state. Generated output is deliberately kept so disabling is reversible; the adapter contract identifies its paths for review, and `intelligence status --check` names the owned paths still on disk and the `intelligence-manage-adapters` skill that removes 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
46
 
47
47
  ## Required interface
48
48
 
@@ -187,7 +187,7 @@ Use the engine library instead of copying parsers or file-handling logic.
187
187
  | `read_yaml_list(config, section)` | Stream entries from `sources.<section>`, package references as their store paths. |
188
188
  | `load_yaml_list(config, section)` | Same list into `IS_YAML_LIST`, cached — no subprocess on repeat reads. |
189
189
  | `get_frontmatter_value(key, file)` | Read a scalar from the first frontmatter block. |
190
- | `frontmatter_index(keys, file...)` | Read several frontmatter scalars for many files in one pass (`\x1f`-separated rows; special key `paths#` counts `paths:` lines). |
190
+ | `frontmatter_index(keys, file...)` | Read several frontmatter scalars for many files in one pass (`\x1f`-separated rows; `paths#` counts `paths:` lines, `paths[]` folds a paths list for display). |
191
191
  | `has_frontmatter(file)` / `has_paths(file)` | Inspect source shape. |
192
192
  | `strip_frontmatter(file)` | Emit the body without its first frontmatter block. |
193
193
  | `get_model(config, tool, tier)` | Resolve a `frontier`, `heavy`, `standard` or `light` model, including manifest overrides. |
@@ -218,16 +218,18 @@ Intelligence routes always-on rules once through `AGENTS.md` for tools that cons
218
218
 
219
219
  | Built-in | Always-on rules | Always-on, not in `AGENTS.md` | Scoped rules |
220
220
  |---|---|---|---|
221
- | `agents` | Inline in `AGENTS.md` | — | List with links |
221
+ | `agents` | Inline in `AGENTS.md` | None | List with links; package and out-of-tree content by name |
222
222
  | `antigravity` | Omit; reads `AGENTS.md` | `.agents/rules/*.md` with `trigger: always_on` | `.agents/rules/*.md` with `trigger: glob` and `globs:` |
223
223
  | `claude` | Copy | Copy | Copy with `paths:` preserved |
224
224
  | `cursor` | Omit; reads `AGENTS.md` | `.mdc` with `alwaysApply: true` | `.mdc` with `globs:` |
225
225
  | `copilot` | Omit; reads `AGENTS.md` | `.instructions.md` with `applyTo: "**"` | `.instructions.md` with `applyTo:` |
226
- | `codex` | Omit; reads `AGENTS.md` | `AGENTS.override.md`: all of `AGENTS.md` plus these rules | No native channel |
226
+ | `codex` | Omit; reads `AGENTS.md` | `AGENTS.override.md`: all of `AGENTS.md` plus these rules | Compact patterns and source links in `AGENTS.override.md`; read on demand |
227
227
  | `pi` | Omit; reads `AGENTS.md` | Added to the system prompt by the generated extension | Generated on-demand files and extension |
228
228
  | `opencode` | Omit; reads `AGENTS.md` | No channel; sync warns | No native channel |
229
229
 
230
- Codex reads `AGENTS.override.md` instead of `AGENTS.md` in the same directory, so the Codex adapter writes it — the whole rendered `AGENTS.md`, then the rules only Codex receives — while such a rule exists, and removes it when none does. Its contract manages and ignores the path; a file there that Intelligence did not generate is never touched, and a render that needs the path refuses until it is moved.
230
+ Codex reads `AGENTS.override.md` instead of `AGENTS.md` in the same directory, so the Codex adapter writes the whole rendered `AGENTS.md`, a compact scoped-rule index, and any additional always-on rules into that override. It exists while either kind needs it. Its contract manages and ignores the path; a file there that Intelligence did not generate is never touched, and a render that needs the path refuses until it is moved.
231
+
232
+ The scoped index pairs source `paths:` patterns with source links and asks Codex to read matching rules before working on files. Local and targeted rules follow the same source filtering as other output. This is model-followed guidance, not a native glob matcher; it creates no nested `AGENTS.md` files and leaves other tools' instruction channels unchanged. Checked 2026-10-23 against OpenAI's [instruction discovery documentation](https://learn.chatgpt.com/docs/agent-configuration/agents-md): startup discovery walks from the project root to the working directory, and an override replaces the base file in the same directory. Restart Codex after syncing updated instructions.
231
233
 
232
234
  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.
233
235
 
@@ -303,7 +305,7 @@ Content shipped in `@ainova-systems/sync` cannot assume the project's content-di
303
305
  | `<manifest>` | `intelligence.yaml` |
304
306
  | `<sync-cmd>` | `intelligence sync` |
305
307
 
306
- 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.
308
+ Call `finalize_output_file` on every text file after transformation. It expands the tokens literally, keeps a token escaped with a backslash as written ([Layout tokens](conventions.md#layout-tokens)), and converts CRLF to LF. A generated file containing an unexpanded token is an adapter bug.
307
309
 
308
310
  `copy_skill_bundle` already finalizes Markdown within a skill; do not run a second custom token pass.
309
311
 
@@ -142,6 +142,8 @@ Expansion applies to frontmatter and bodies. Thus `paths: ["<content-dir>/**"]`
142
142
 
143
143
  Project-authored artifacts normally use their known project paths directly. Tokens are useful only when the same artifact must work under different content-directory or package-store locations.
144
144
 
145
+ The four tokens are reserved in every source, so a placeholder that means something else, such as a module name, needs a backslash: `\<module>` renders as `<module>`. Sync prints a `WARNING:` line with the file and line when a project source holds a token joined to the text around it (`<module>_update_N`, `modules/<module>.md`) or used as an XML element closed on the same line (`<manifest></manifest>`).
146
+
145
147
  ## Naming
146
148
 
147
149
  Rule filenames, agent names and skill names share a domain prefix such as `backend-`, `frontend-`, `devops-`, `core-`, `tests-`, a project codename or a monorepo component. Pick the domain from repository structure and reuse it.
@@ -251,7 +253,7 @@ Skills take the same field. `.claude/skills/` receives Claude's level, the share
251
253
 
252
254
  ### Access mappings
253
255
 
254
- `access: full` inherits ordinary tool permissions. `access: readonly` is transformed into the target's native restriction: for example Claude receives read/search/bash tools with writes disallowed, Cursor receives `readonly: true`, and Codex receives a read-only sandbox.
256
+ `access: full` inherits ordinary tool permissions. `access: readonly` is transformed into the target's native restriction, and the targets differ on commands. Claude keeps `Bash` beside its read and search tools, with `Write` and `Edit` disallowed. Cursor receives `readonly: true`, which its documentation says blocks edits and state-changing shell commands. Copilot, OpenCode and Antigravity receive read and search tools only, and Pi receives the restriction as an instruction. Codex receives `sandbox_mode = "read-only"`, which its [subagent documentation](https://learn.chatgpt.com/docs/agent-configuration/subagents) says a custom agent file cannot set, so a Codex agent runs in the parent session's sandbox.
255
257
 
256
258
  Use only `full` or `readonly` in source agents. Tool-specific permission syntax belongs in adapters.
257
259
 
@@ -279,13 +281,13 @@ Rule content in Markdown.
279
281
 
280
282
  | Source rule | Claude | Cursor | Antigravity | Copilot | Codex / Pi / OpenCode / `AGENTS.md` |
281
283
  |---|---|---|---|---|---|
282
- | Scoped | copied with `paths:` | `.mdc` with `globs:` | `.md` with `trigger: glob` and `globs:` | `.instructions.md` with `applyTo:` | listed in `AGENTS.md`; Pi also gets on-demand files and an extension |
284
+ | Scoped | copied with `paths:` | `.mdc` with `globs:` | `.md` with `trigger: glob` and `globs:` | `.instructions.md` with `applyTo:` | listed in `AGENTS.md`; Codex gets routing in its override; Pi gets on-demand files and an extension |
283
285
  | Always-on | copied | omitted | omitted | omitted | inlined once into `AGENTS.md` |
284
286
  | Always-on, not in `AGENTS.md` | copied | `.mdc` with `alwaysApply: true` | `.md` with `trigger: always_on` | `.instructions.md` with `applyTo: "**"` | Codex: `AGENTS.override.md`; Pi: added to the system prompt by its extension; OpenCode: none |
285
287
 
286
- Cursor, Copilot, Codex, Pi, OpenCode and Antigravity consume `AGENTS.md`, so always-on rules are not duplicated in their tool-specific channels. Claude does not consume `AGENTS.md`, so it receives the full rule set. OpenCode and Codex have no generated path-scoped rule channel; OpenCode users may configure `instructions:` globs themselves.
288
+ Cursor, Copilot, Codex, Pi, OpenCode and Antigravity consume `AGENTS.md`, so always-on rules are not duplicated in their tool-specific channels. Claude receives the full rule set in `.claude/rules/`. Claude Code 2.1.277 and later also reads `AGENTS.md` when no `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` exists in the working directory or above it ([memory docs](https://code.claude.com/docs/en/memory#agents-md)), so the always-on rules load twice until `.claude/settings.json` carries `"claudeMdExcludes": ["**/AGENTS.md"]`. Sync never edits that file; `intelligence status --check` prints the entry when it is missing. Codex's override lists scoped patterns and source links with an instruction to read matching rules on demand; this depends on the model following the instruction, not native glob-triggered loading. OpenCode users may configure `instructions:` globs themselves. See [adapter routing](adapters.md#rules) for delivery details.
287
289
 
288
- An always-on rule `AGENTS.md` does not carry — a local one, or one whose set leaves `agents` out (see [Selecting adapters](#selecting-adapters)) — reaches each tool in its set through that tool's own channel instead, the last row. Codex reads `AGENTS.override.md` instead of `AGENTS.md` in the same directory, so the file holds the whole `AGENTS.md` plus the rules only Codex receives; it is ignored, exists only while such a rule does, and one Intelligence did not generate is never touched. Antigravity's `trigger: always_on` belongs to the same undocumented key family as its `trigger: glob`. OpenCode reads instructions only from `AGENTS.md` or its committed `opencode.json`, so it can receive neither a local nor a tool-targeted always-on rule.
290
+ An always-on rule `AGENTS.md` does not carry — a local one, or one whose set leaves `agents` out (see [Selecting adapters](#selecting-adapters)) — reaches each tool in its set through that tool's own channel instead, the last row. Codex reads `AGENTS.override.md` instead of `AGENTS.md` in the same directory, so the file holds the whole `AGENTS.md` plus scoped routing and additional always-on rules; it is ignored, exists while either needs it, and one Intelligence did not generate is never touched. Antigravity's `trigger: always_on` belongs to the same undocumented key family as its `trigger: glob`. OpenCode reads instructions only from `AGENTS.md` or its committed `opencode.json`, so it can receive neither a local nor a tool-targeted always-on rule.
289
291
 
290
292
  Keep always-on rules small. Put narrow framework or component guidance behind `paths:` so unrelated tasks do not pay its context cost.
291
293
 
@@ -308,9 +310,15 @@ argument-hint: "<route-name>"
308
310
  4. Report the changed route and verification.
309
311
  ```
310
312
 
311
- Standard optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) and tool extensions pass through unchanged. A tool ignores fields it does not understand. `effort:` is the exception: sync renders it per tool, as [Effort mappings](#effort-mappings) describes. `targets:` and `exclude_targets:` select the adapters that receive the skill and are removed from every copy ([Selecting adapters](#selecting-adapters)).
313
+ Standard optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) and tool extensions pass through unchanged. `effort:` is the exception: sync renders it per tool, as [Effort mappings](#effort-mappings) describes. `targets:` and `exclude_targets:` select the adapters that receive the skill and are removed from every copy ([Selecting adapters](#selecting-adapters)).
314
+
315
+ `disable-model-invocation: true` makes a skill one only a person starts, as a slash command. The flag hides the procedure, not the act: Claude Code keeps the skill's description out of the model's context, so a request in words such as "commit this" never reaches the skill and is carried out without its checks. Keep the flag for a skill that starts a named person's decision, such as approving a plan, moving versions or changing adapters; the person typing the command after seeing what it acts on is the yes, so the skill does not ask again. A skill with side effects stays model-invocable, and its description names the requests that should reach it.
316
+
317
+ Claude Code, Cursor, Copilot, OpenCode and Pi read the field itself; Codex does not, so sync writes `agents/openai.yaml` with `allow_implicit_invocation: false` beside the skill in `.agents/skills/`. Keep the source to the field. A skill that ships its own `agents/openai.yaml` keeps everything else in it: sync adds the policy when the file sets none, and refuses one that sets it otherwise or not as a plain `allow_implicit_invocation: false` directly under `policy:`.
312
318
 
313
- `disable-model-invocation: true` makes a skill one only the owner starts, as a slash command: use it for a procedure that changes versions, the lock or generated outputs, where an agent selecting it unasked is the failure. Claude Code, Cursor and Copilot read the field itself; Codex does not, so sync writes `agents/openai.yaml` with `allow_implicit_invocation: false` beside the skill in `.agents/skills/`. Keep the source to the field. A skill that ships its own `agents/openai.yaml` keeps everything else in it: sync adds the policy when the file sets none, and refuses one that sets it otherwise or not as a plain `allow_implicit_invocation: false` directly under `policy:`.
319
+ `context: fork`, `agent`, `background` and `model` are Claude Code fields, and sync copies them unchanged. A forked skill runs as a subagent of the `agent` type that receives the skill as its prompt, sees none of the conversation and has no `AskUserQuestion` tool. In an interactive session it runs in the background by default, so the caller's turn goes on before the result arrives, and a skill whose result its caller needs in the same turn sets `background: false` ([skills](https://code.claude.com/docs/en/skills#run-skills-in-a-subagent), [subagents](https://code.claude.com/docs/en/sub-agents#available-tools)). Without `context: fork`, `agent` selects nothing, and `model` switches the session's model for the rest of the turn, which then re-reads the whole conversation without the prompt cache ([prompt caching](https://code.claude.com/docs/en/prompt-caching#switching-models)). Of the other targets, only Copilot in VS Code documents `context: fork`, as experimental, and none documents `agent`, `background` or `model` for a skill (checked 2026-11-07).
320
+
321
+ Fork a skill whose inputs are files and arguments, that asks nothing on the way, and whose work is heavy reading, auditing or judging: the caller's context stays small and the work gets an independent reader. Keep a skill inline, with no `agent`, when its input is the conversation or its job is a dialogue or an approval with the person. A forked skill never asks. When it reaches a decision that belongs to the person, it stops before the write and ends its result with the decision, the options and its recommendation, so the caller asks.
314
322
 
315
323
  These limits reject a skill instead of degrading it:
316
324
 
@@ -351,14 +359,15 @@ The engine copies the complete skill directory. Promote a helper outside the ski
351
359
 
352
360
  A symlink inside a skills source renders as the file or directory it points at when that stays inside the same source, so one source can alias a skill or share a resource between skills. A link that leaves its source, dangles or points at a directory enclosing it is left out with a `WARNING:` line; a skill whose directory or `SKILL.md` is such a link is left out entirely. Add a skill kept elsewhere as its own source or package instead of linking it in. Generated skill directories never contain symlinks.
353
361
 
354
- Size limits are backstops, not quotas:
362
+ Size limits are backstops, not quotas. They count words, because a paragraph written as one long line passes any line cap:
355
363
 
356
- | Artifact | Hard cap | Response |
364
+ | Artifact | Budget | Response |
357
365
  |---|---|---|
358
- | `SKILL.md` body | 1000 lines | Move detail to `references/` |
359
- | Reference file | 500 lines | Add a contents list past 300; split if still oversized |
360
- | Rule | 500 lines | Split by scope or move examples behind a skill reference |
361
- | Agent | 200 lines | Move procedures and constraints into skills/rules |
366
+ | `SKILL.md` | 1,500 words | Move detail to `references/` |
367
+ | Reference file | 3,000 words | Add a contents list past 1,500; split if still oversized |
368
+ | Rule | 2,000 words | Split by scope or move examples behind a skill reference |
369
+ | Agent | 500 words | Move procedures and constraints into skills/rules |
370
+ | Paragraph, list item or table row | 150 words | Split it into the thoughts it holds |
362
371
 
363
372
  ## Writing discipline
364
373
 
@@ -448,6 +457,8 @@ targets:
448
457
 
449
458
  `AGENTS.md` stays committed by default: it is the tool-neutral fallback every tool reads, and a fresh clone has it only when it is committed, before anyone has synced.
450
459
 
460
+ Claude Code loads project skills from the checkout a session starts in, and from 2.1.277 a Git worktree without its own `.claude/skills`, such as one not yet synced, gets the main checkout's ([skills docs](https://code.claude.com/docs/en/skills#load-skills-in-monorepos-and-subdirectories)), so a task in a worktree runs in a session started there after `intelligence sync` there.
461
+
451
462
  Git tracking and release packaging are separate policies. When a project
452
463
  already has `.vscodeignore`, `.npmignore`, or `.dockerignore`, the CLI appends a
453
464
  small idempotent block excluding the package store, `intelligence.yaml`,
@@ -21,7 +21,7 @@ Three ways to shorten, in order of what they are worth:
21
21
  2. **Delete what the code already says.** A rule restating what a reader can see in the file is noise, and noise trains people to skim the lines that are not.
22
22
  3. **Cut the words, keep the reason.** Prose can shrink; the *why* cannot — an instruction without its reason does not survive contact with a judgement call.
23
23
 
24
- **Never solve a problem by adding an artifact when moving one, or removing one, would do.** A new rule is the most expensive answer available, and it is the one that feels cheapest to write.
24
+ **Never solve a problem by adding an artifact when moving one, or removing one, would do.** A new rule is the most expensive answer available, and it is the one that feels cheapest to write. The same holds inside an artifact: a fix restates the outcome once, and a clause added for each edge case or review finding is itself the defect.
25
25
 
26
26
  ## Source of truth
27
27
 
@@ -60,7 +60,7 @@ The mistakes that actually happen, in order of frequency:
60
60
 
61
61
  ### Invariants
62
62
 
63
- - **Never state behaviour of a tool or engine you have not verified in its documentation or source.** An unverified claim about tooling is worse than a gap: nothing in the repository contradicts it, so it silently reshapes every decision downstream. Verified means a page you can cite - for how rules load, the Claude Code docs *Memory → Organize rules with `.claude/rules/`* and *Subagents → What loads at startup*.
63
+ - **Never state behaviour of a tool or engine you have not verified in its documentation or source.** An unverified claim about tooling is worse than a gap: nothing in the repository contradicts it, so it silently reshapes every decision downstream. Verified means a page you can cite - for how rules load, the Claude Code [memory](https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/) and [subagent startup](https://code.claude.com/docs/en/sub-agents#what-loads-at-startup) docs.
64
64
  - **Never write a current defect into a rule as if it were the design.** Known breakage belongs in one place that says so. Every other rule describes the project *as it is meant to work* — a workaround documented as procedure becomes permanent.
65
65
  - **Never link from one always-on rule to another.** Always-on rules are inlined verbatim into `AGENTS.md`, and the path-scoped channels carry only scoped rules, so a relative link is dead in at least one output. Name the rule instead; it loads on its own.
66
66
 
@@ -68,7 +68,7 @@ The mistakes that actually happen, in order of frequency:
68
68
 
69
69
  An agent is **thin**: who it is, where it stops, how it verifies. Nothing else.
70
70
 
71
- - **Do not list rules for an agent to read.** They load on their own — Claude Code loads `.claude/rules/` into every custom subagent's startup context, and Cursor, Copilot, Codex, Pi, OpenCode and Antigravity receive always-on rules inlined in `AGENTS.md`. Naming a rule is fine; copying it is duplication that drifts.
71
+ - **Do not list rules for an agent to read.** They load on their own: a Claude Code subagent starts with the project's instruction files, `.claude/rules/` included, unless it is the built-in Explore or Plan agent or its definition sets `omitClaudeMd`, and Cursor, Copilot, Codex, Pi, OpenCode and Antigravity receive always-on rules inlined in `AGENTS.md`. A step handed to an agent that starts without them names the rules it depends on in the delegation message. Naming a rule is fine; copying it is duplication that drifts.
72
72
  - **Point at a rule, do not restate it.** Wanting to copy a rule into an agent means the rule is in the wrong place — move it.
73
73
  - **Do carry** what is genuinely the agent's own: its boundaries ("if the app will not start, stop — do not hand-write the output"), its verification commands, its definition of done.
74
74
 
@@ -95,15 +95,16 @@ Two verbs are told apart by what already exists. **`add-` puts one new member in
95
95
 
96
96
  ## Size — the backstop, not the goal
97
97
 
98
- The goal is subtraction, above. These are only the line past which something is definitely wrong.
98
+ The goal is subtraction, above. These are only the line past which something is definitely wrong. They count words, because a line cap never sees a paragraph written as one long line.
99
99
 
100
- | Type | Hard cap |
100
+ | Type | Budget |
101
101
  |---|---|
102
- | Rule | 500 lines |
103
- | Agent | 200 lines |
104
- | `SKILL.md` | 1000 lines |
102
+ | Rule | 2,000 words |
103
+ | Agent | 500 words |
104
+ | `SKILL.md` | 1,500 words |
105
+ | Paragraph, list item or table row | 150 words |
105
106
 
106
- **Ceilings, not quotas.** An artifact that says everything it needs to is finished, not underweight — nothing here is a reason to pad. Over the cap means the artifact is doing two jobs, or the detail belongs in `references/<topic>.md` with a pointer from the body.
107
+ **Ceilings, not quotas.** An artifact that says everything it needs to is finished, not underweight; nothing here is a reason to pad. Over the budget means the artifact is doing two jobs, or the detail belongs in `references/<topic>.md` with a pointer from the body, and a paragraph over it holds several thoughts to split.
107
108
 
108
109
  `description` fields compete for one shared budget across the whole registry: a long one pushes another artifact out of reach. Four to eight words when the name is unambiguous; longer only when a sibling does something similar and needs distinguishing. (The tools reject a `description` over 1024 characters outright — but that is a wall to stay far away from, not a target.)
109
110
 
@@ -104,10 +104,13 @@ explains itself well.
104
104
  back. Completion requires
105
105
  `IS_STATUS=ok`, a clean final check, and no `onboarding is pending` header
106
106
  after accepted migration.
107
- 11. Report what was created, updated, removed, or deliberately kept. Remind the
108
- user to commit source, manifest, lock and `AGENTS.md` changes, plus Copilot's
109
- `.github/` output only when `targets.copilot.commit_output` is `true`. Keep or
110
- remove the initial backup only by separate user approval.
107
+ 11. Report what was created, updated, removed, or deliberately kept. A
108
+ `status --check` note that names a tool settings change is the owner's step:
109
+ show it with the exact JSON it prints and leave the settings file to the
110
+ owner. Remind the user to commit source, manifest, lock and `AGENTS.md`
111
+ changes, plus Copilot's `.github/` output only when
112
+ `targets.copilot.commit_output` is `true`. Keep or remove the initial backup
113
+ only by separate user approval.
111
114
 
112
115
  ## Later learning
113
116
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: intelligence-learn-from-session
3
3
  description: "Capture session lessons and workflows in project context"
4
- agent: intelligence-architect
4
+ argument-hint: "[lesson, or a folder of lesson records]"
5
5
  ---
6
6
 
7
7
  # Learn from a session
@@ -9,6 +9,8 @@ agent: intelligence-architect
9
9
  Capture a durable preference, working pattern, recurring friction, or successful
10
10
  workflow from a session in an established Intelligence project. This skill owns
11
11
  identifying and generalizing the lesson; `intelligence-update-context` owns writing it.
12
+ A lesson is saved only through that skill, as a source every clone and tool reads,
13
+ never in a tool's own memory.
12
14
 
13
15
  ## Verify readiness
14
16
 
@@ -23,7 +25,10 @@ identifying and generalizing the lesson; `intelligence-update-context` owns writ
23
25
 
24
26
  2. Identify the lesson from the conversation or explicit user input. For a
25
27
  workflow, list the actual steps performed, user decisions, branches, failure
26
- recovery, and verification. Retain the sequence that worked.
28
+ recovery, and verification. Retain the sequence that worked. Given a folder
29
+ of lesson records, read every record against the current sources: report a
30
+ record the sources already satisfy as applied and drop it, and carry the rest
31
+ forward as lessons.
27
32
  3. Generalize the evidence by removing instance-specific filenames, dates, and
28
33
  phrasing. Keep the reason needed to apply the lesson to the next task. Prefer
29
34
  a positive instruction such as "Default to one recommendation" for "Stop
@@ -33,18 +38,26 @@ identifying and generalizing the lesson; `intelligence-update-context` owns writ
33
38
  4. Inspect configured sources for an existing owner. A preference or constraint
34
39
  belongs in a rule, path-specific context in a scoped rule, an observed
35
40
  repeatable procedure in a skill, and a persona or expertise boundary in an
36
- agent. Prefer extending an existing artifact over adding a sibling.
37
- 5. Present each proposal with its action (`CREATE`, `UPDATE`, or `ARCHIVE`),
38
- source path, concrete draft, and one-line reason. This phase is read-only;
39
- only user-accepted lessons become persistent instructions. Reuse approval
40
- already given for the exact proposal.
41
+ agent. Prefer extending an existing artifact over adding a sibling. A lesson
42
+ about installed package content or the CLI itself is fixed upstream, never in
43
+ the installed copy: draft an issue for the package's source repository, which
44
+ `intelligence.lock` records, or for Intelligence. The draft holds only safe
45
+ facts: versions from `intelligence version`, what was done, the expected and
46
+ the actual behavior, and nothing confidential or specific to the project.
47
+ 5. Present each proposal with its action (`CREATE`, `UPDATE`, `ARCHIVE`, or
48
+ `UPSTREAM` for an issue draft), source path or repository, concrete draft, and
49
+ one-line reason. This phase is read-only; only user-accepted lessons become
50
+ persistent instructions. Reuse approval already given for the exact proposal.
41
51
 
42
52
  ## Apply and verify
43
53
 
44
54
  6. Pass accepted proposals and their session evidence to
45
55
  `/intelligence-update-context`. It handles artifact-specific authoring,
46
- references, a single batch sync, and the final status check.
56
+ references, a single batch sync, and the final status check. An accepted
57
+ `UPSTREAM` draft goes into the report; filing it is a separate request the
58
+ person makes.
47
59
  7. Compare the resulting source changes with the accepted lesson. For a workflow,
48
60
  verify that its decisions, working steps, recovery, and proof of completion
49
61
  remain executable without this conversation. Report the saved lesson, its
50
- owner, and verification; a session-specific transcript is not completion.
62
+ owner, and verification, the records dropped as applied, and each upstream
63
+ draft with its state; a session-specific transcript is not completion.
@@ -2,7 +2,6 @@
2
2
  name: intelligence-manage-adapters
3
3
  description: "Enable, disable, or remove adapters and assess generated-output cleanup"
4
4
  argument-hint: "<enable|disable|remove> <adapter-name>"
5
- agent: intelligence-operator
6
5
  disable-model-invocation: true
7
6
  ---
8
7
 
@@ -42,5 +41,6 @@ scaffolding, and sync. Adapter implementation follows the public adapter guide.
42
41
  5. Verify with `intelligence adapter list` and `intelligence status --check`:
43
42
  requested target state is correct, retained files are intact, and only approved
44
43
  adapter-owned output was deleted. Report state, output paths, and any remaining
45
- implementation or cleanup work. Preserve CLI refusals instead of recreating
46
- lifecycle mechanics manually.
44
+ implementation or cleanup work, with each settings change a check note names
45
+ as the owner's step and its exact JSON. Preserve CLI refusals instead of
46
+ recreating lifecycle mechanics manually.
@@ -3,33 +3,37 @@ name: intelligence-review-context
3
3
  description: "Audit rules, agents, and skills and propose behavior-preserving reductions"
4
4
  argument-hint: "[rules|agents|skills|all] [compact]"
5
5
  agent: intelligence-architect
6
+ context: fork
7
+ background: false
6
8
  ---
7
9
 
8
10
  # Review project context
9
11
 
10
- Audit the intelligence layer and propose improvements. Review and compaction
11
- analysis are read-only: do not sync or edit while inspecting. Accepted changes
12
- go through `intelligence-update-context`; this skill owns their review criteria.
12
+ Audit the intelligence layer and return proposals. Work from the arguments and
13
+ the files alone and ask nothing: in Claude Code this skill runs as a subagent that
14
+ sees none of the conversation. Review and compaction analysis are read-only, so
15
+ never sync or edit. Accepted changes go through `intelligence-update-context`;
16
+ this skill owns their review criteria.
13
17
 
14
18
  ## Audit
15
19
 
16
20
  1. Read `<manifest>` and enumerate its configured rule, agent, and skill sources.
17
21
  Read the `intelligence-authoring` rule and
18
22
  `<module>/references/conventions.md`. Review project-owned sources; an installed
19
- package finding — an entry naming a package's directory, however spelled —
23
+ package finding, an entry naming a package's directory however spelled,
20
24
  belongs upstream. In a package's own repository, review its
21
25
  authoritative source tree. Do not audit generated output prose. Its byte count
22
26
  is the metadata-only exception.
23
27
  2. Record source line and byte counts, and git history when available: first
24
28
  addition, last edit, and edit count. Find incoming references before proposing
25
29
  an archive. Resolve the shared agents output from the manifest and measure its
26
- bytes, or reuse a fresh `CONTEXT:` summary. Mark missing or stale measurements;
27
- do not regenerate output during analysis.
30
+ bytes. Mark missing or stale measurements; do not regenerate output during
31
+ analysis.
28
32
  3. Read and apply [references/audit-checks.md](references/audit-checks.md). Reuse
29
33
  these generic checks in any project-specific audit. Check subtraction before
30
34
  proposing a split or rewrite: remove an unnecessary artifact, merge overlapping
31
35
  owners, or replace prose with an existing deterministic gate where possible.
32
- 4. When the user asks to compact context, or the audit proposes a merge, move,
36
+ 4. When the arguments ask to compact context, or the audit proposes a merge, move,
33
37
  deletion, scoping change, or size reduction, read
34
38
  [references/compaction.md](references/compaction.md) and its principles. Build
35
39
  the behavior ledger and draft the structural reduction before wording changes.
@@ -39,17 +43,17 @@ go through `intelligence-update-context`; this skill owns their review criteria.
39
43
  3: description or naming polish). Compaction items also show the authoritative
40
44
  owner, behavior preserved, estimated byte savings, and any change to meaning,
41
45
  scope, or loading. Surface unverified reasons without silently rewriting them.
46
+ A finding about what an artifact says is a wrong or missing outcome, a
47
+ contradiction, or a stale reference; an edge case its outcome already covers
48
+ is no finding.
42
49
 
43
- ## Accepted changes
50
+ ## Hand the decision back
44
51
 
45
- 6. The user accepts items individually; a batch acceptance may cover named items.
46
- Pass accepted proposals to `/intelligence-update-context`, with the behavior
47
- ledger and additional checks. Reuse existing approval for those exact changes.
48
- If fresh rendered measurements are needed, capture them only after apply is
49
- authorized and before editing; record `<sync-cmd> --compact` output and require
50
- `IS_STATUS=ok`. The authoring skill performs the final batch sync and status check.
51
- 7. Verify each accepted finding against the resulting sources. For reductions,
52
- complete the ledger comparison, measurements, and behavioral evaluation in
53
- the compaction reference. Report each artifact as pass, fixed (what), or
54
- flagged (for whom), plus unresolved proposals. A read-only review ends with
55
- the evidenced punch-list; an apply run ends only after its checks complete.
52
+ 6. End the result with the decision that belongs to the person: which numbered
53
+ items to accept, one by one or as a named batch, with the items you recommend
54
+ and the reason for each. Accepted items go to `/intelligence-update-context`
55
+ with their drafts. For compaction items, include what that skill needs to
56
+ verify them: the behavior ledger, the baseline counts with any missing
57
+ rendered measurement marked, and the steps under *Verify the accepted result*
58
+ in the compaction reference, copied into the result. The review is complete
59
+ when this evidenced punch-list is returned.
@@ -6,7 +6,8 @@ Apply these checks to the source groups resolved by the review skill.
6
6
  |---|---|---|
7
7
  | **Duplicate content** | Two artifacts cover overlapping scope, or their descriptions share trigger phrases | `MERGE` — present both, propose one |
8
8
  | **Misplaced content** | A checklist or procedure in an agent body; a convention in an agent; a workflow in a rule; expertise in a skill (the *Pick the right artifact* table in `intelligence-authoring`) | `MOVE` — a move, not a rewrite: both files change together |
9
- | **Over the cap** | `SKILL.md` over 1000 lines, rule over 500, agent over 200 | `SPLIT` — two artifacts, or move detail into `references/<topic>.md` |
9
+ | **Over the budget** | `SKILL.md` over 1,500 words, rule over 2,000, agent over 500, skill reference over 3,000, or a paragraph, list item or table row over 150 (`R7`) | `SPLIT` - two artifacts, detail into `references/<topic>.md`, or a long paragraph into the thoughts it holds |
10
+ | **Rule repeats a gate** | A rule sentence restating what a script, test or sync already checks or refuses | `DELETE` - the gate holds the decision; a second copy in prose drifts from it and costs context in every session |
10
11
  | **Shared instruction budget** | The measured shared agents output, or `agents-md` in sync's `CONTEXT:` summary, is over 32 KiB (32,768 bytes) | `COMPACT` — this is Intelligence's recommended maximum, not an adapter rejection threshold; use the compaction procedure in `intelligence-review-context` to reduce the owning sources without teaching terse output |
11
12
  | **Rule links to a rule** | A markdown link from one rule to another (`R1`) | `UNLINK` — name the rule instead: always-on rules are inlined into `AGENTS.md` and the scoped channels carry only scoped rules, so the link is dead in at least one output |
12
13
  | **Machine facts in a rule** | OS, shell, editor or a local absolute path (`R2`) | `MOVE` — these belong in a local rule, `<content-dir>/rules/<name>.local.md`, which Git ignores and sync keeps out of committed output; a rule is committed and read by everyone, including whoever is on another platform |
@@ -14,7 +15,7 @@ Apply these checks to the source groups resolved by the review skill.
14
15
  | **Skill with no verification** | Nothing at the end proves the procedure worked (`R4`) | `FLAG` — a procedure that proves nothing is a note, or just the work: give it a verification, or delete it |
15
16
  | **Pressure marker** | A heading or section named CRITICAL / MANDATORY / HARD RULE / READ FIRST, or a density of `MUST` / `NEVER` / `ALWAYS` with no reason beside it (`R5`) | `REWRITE` - plain heading, one reason per constraint; when several instructions are each marked critical the marker stops carrying information, so keep emphasis for the one instruction demonstrably under-weighted without it |
16
17
  | **History narrative** | A PR number, incident id, commit SHA, date or "this session" inside a rule body (`R6`) | `REWRITE` - keep the causal sentence, drop the archaeology; a rule's authority is the behaviour it prescribes, and git history keeps the date |
17
- | **Reserved prefix** | A project artifact named `intelligence-*` | `RENAME` — the prefix belongs to the sync package and collides in generated output |
18
+ | **Name collision** | A project artifact named `intelligence-*` or like an installed package artifact it does not mean to override, or a skill named like a built-in command or bundled skill of an enabled tool ([Claude Code commands](https://code.claude.com/docs/en/commands)) | `RENAME` - the project artifact renders in the package's place, and a Claude Code skill replaces the command of its name but not that command's aliases, so one request runs different procedures depending on the name typed |
18
19
  | **Naming** | A skill that is not `<domain>-<verb>-<noun>`, or a domain invented rather than reused | `RENAME` — or introduce the new domain deliberately |
19
20
  | **Stale** | No edits in 6+ months and nothing cross-references it | `ARCHIVE` — move to `<content-dir>/_archive/` |
20
21
  | **Negative-framed judgement call** | "Never do X" where a positive default fits, outside safety / security / output-format | `REWRITE` — state the default; reserve NEVER for true must-nots |
@@ -23,6 +24,7 @@ Apply these checks to the source groups resolved by the review skill.
23
24
  | **Weak / duplicate description** | Identical to a sibling, or too vague to choose between them | `DIFFERENTIATE` — add the distinguishing trigger |
24
25
  | **Description over budget** | Over ~250 characters (the shared registry budget); over **1024** the tools reject the artifact outright | `TRIM` — keep the distinguishing trigger, drop the rest |
25
26
  | **Missing frontmatter field** | `name` or `description` absent | `PATCH` — add it |
27
+ | **Dead skill field** | `agent:` in a `SKILL.md` without `context: fork` (`R8`) | `PATCH` - delete `agent:`, which selects nothing without the fork, or fork the skill when the authoring conventions say it should |
26
28
  | **Orphan rule** | Nothing points at it and nothing loads it | `FLAG` — intentional, or dead? |
27
29
 
28
30
  Detection commands, portable on purpose — no `\b` and no `-P`, so the same command works in Git Bash on Windows, on macOS (BSD grep) and on Linux. Run each over the source directories resolved in step 2, never over generated output:
@@ -51,6 +53,28 @@ grep -rcE '(MUST|NEVER|ALWAYS)' <rule-dirs> <agent-dirs> <skill-dirs> | grep -vE
51
53
  # R6 - history narrative in a rule body: PR numbers, incident ids, dates, "this session", commit SHAs
52
54
  grep -rniE -e '(#|PR )[0-9]{3,}' -e '[0-9]{4}-[0-9]{2}-[0-9]{2}' -e 'this session|origin session' \
53
55
  -e '`[0-9a-f]{7,40}`' <rule-dirs>
56
+
57
+ # R7 - words per file, then the longest paragraph; a list item or table row is its own paragraph
58
+ find <rule-dirs> <agent-dirs> <skill-dirs> -name '*.md' | while IFS= read -r f; do
59
+ awk -v f="$f" 'BEGIN { fence = sprintf("%c%c%c", 96, 96, 96) }
60
+ NR == 1 && /^---[ \t\r]*$/ { fm = 1; next }
61
+ fm { if (/^---[ \t\r]*$/) fm = 0; next }
62
+ { w += NF }
63
+ index($1, fence) == 1 { code = !code; p = 0; next }
64
+ code || NF == 0 { p = 0; next }
65
+ /^[ \t]*(#|[-*] |[0-9]+\. |\|)/ { p = 0 }
66
+ { p += NF; if (p > max) max = p }
67
+ END { printf "%6d %4d %s\n", w, max, f }' "$f"
68
+ done | sort -rn
69
+
70
+ # R8 - a skill's agent: without context: fork, read from the frontmatter only
71
+ find <skill-dirs> -name SKILL.md | while IFS= read -r f; do
72
+ awk -v f="$f" 'NR == 1 && /^---[ \t\r]*$/ { fm = 1; next }
73
+ fm && /^---[ \t\r]*$/ { exit }
74
+ fm && /^agent:/ { a = 1 }
75
+ fm && /^context:[ \t]*"?fork/ { c = 1 }
76
+ END { if (a && !c) print f }' "$f"
77
+ done
54
78
  ```
55
79
 
56
80
  `R4` is a coarse net, not a verdict: a skill that merely *mentions* a verification command anywhere passes it. Read the final step of every skill regardless — the question is whether something at the end **proves the work landed**, not whether the word appears. So are `R5` and `R6`: a `NEVER` that carries its reason and a date inside a frontmatter template are both legitimate hits, and the row's action applies only where the marker or the id is doing no work.
@@ -35,12 +35,14 @@ product requirement.
35
35
 
36
36
  Proposals use `DELETE`, `MERGE`, `SCOPE`, `MOVE`, `REFERENCE`, or `REWRITE` and name
37
37
  the owner, semantic contract, estimated savings, and any changed behavior or load
38
- timing. Keep unapproved passages byte-for-byte; the review skill routes only
39
- accepted proposals to the shared authoring procedure.
38
+ timing. Keep unapproved passages byte-for-byte; only accepted proposals reach
39
+ the shared authoring procedure, which runs the steps below.
40
40
 
41
41
  ## Verify the accepted result
42
42
 
43
- 1. Compare the ledger with the diff. Each original behavior exists once in its
43
+ 1. Before editing, when the baseline marks a rendered measurement missing, record
44
+ `<sync-cmd> --compact` output and require `IS_STATUS=ok`. After editing,
45
+ compare the ledger with the diff. Each original behavior exists once in its
44
46
  authoritative owner, was deliberately removed with approval, or is enforced by
45
47
  the named deterministic mechanism. Check for dead links, unconditional reference
46
48
  loads, conflicting instructions, and unintended scope expansion.
@@ -54,4 +56,4 @@ accepted proposals to the shared authoring procedure.
54
56
  and an ordinary explanation that reveals clipped language. Report explicitly
55
57
  when this evaluation is unavailable: fewer bytes prove size, not quality.
56
58
  4. Report artifacts changed, behavior checks, measurements, and remaining owner
57
- decisions. Return these results to the review skill's final report.
59
+ decisions in the authoring skill's final report.
@@ -3,6 +3,7 @@ name: intelligence-sync
3
3
  description: "Sync intelligence to enabled adapters"
4
4
  agent: intelligence-operator
5
5
  context: fork
6
+ background: false
6
7
  ---
7
8
 
8
9
  # Sync intelligence
@@ -19,13 +20,12 @@ context: fork
19
20
  `IS_STATUS=needs-update` (exit 6) means the project needs a migration only
20
21
  `intelligence update` applies; report that, and never edit the manifest or
21
22
  lock by hand to get past it. A frozen restore refusal means the
22
- manifest and lock disagree or the locked commit cannot be verified/restored—report it without
23
- hand-copying package content.
24
- 4. On a probable Intelligence defect, first ask whether to collect a sanitized
25
- issue draft; collect nothing before approval and never use telemetry. Include
26
- only approved CLI/version, expected/actual behavior, and safe error details—
27
- no confidential or project-specific data. Show the draft and obtain separate
28
- approval before filing. If declined, ask whether the opt-out is for this
29
- session, a user/gitignored dev-project profile, or a shared team policy; save
30
- it only with separate approval. Put team policy in a project-owned rule, and
31
- never invent a profile path or infer the intended scope.
23
+ manifest and lock disagree or the locked commit cannot be verified or restored;
24
+ report it without hand-copying package content.
25
+ 4. On a probable Intelligence defect, end the result with a block headed
26
+ `Probable Intelligence defect` that holds only safe facts: the CLI and engine
27
+ versions from `intelligence version`, the command, the expected and the actual
28
+ behavior, and error text with nothing confidential or specific to the project.
29
+ Follow it with an issue draft built from those facts alone. The result is the
30
+ only place they go: send and file nothing, and use no telemetry. Filing the
31
+ draft is a separate request the person makes.
@@ -2,7 +2,6 @@
2
2
  name: intelligence-update-context
3
3
  description: "Create, revise, or remove project rules, agents, and skills"
4
4
  argument-hint: "[rule|agent|skill] [request or accepted proposals]"
5
- agent: intelligence-architect
6
5
  ---
7
6
 
8
7
  # Update project context
@@ -74,3 +73,6 @@ the same procedure. Updating the layer can create a new artifact.
74
73
  9. Report the created, updated, removed, or archived artifacts and their checks.
75
74
  Return control to the originating workflow for its additional semantic,
76
75
  compaction, migration, or packaging verification; those checks remain required.
76
+ Review proposals carry their verification steps instead, because the review
77
+ has already returned: run each at the point it names, before or after editing,
78
+ and report the results here.
@@ -22,15 +22,16 @@
22
22
  name: <domain>-<verb>-<noun>
23
23
  description: "What the skill does and when to use it"
24
24
  argument-hint: "Expected arguments"
25
- agent: <existing-agent>
26
25
  ---
27
26
  ```
28
27
 
29
28
  Omit optional fields that do not apply. Add `effort:` only when the procedure
30
29
  needs a reasoning effort other than the session's, using the scale in the
31
- authoring conventions' effort mappings. Add the skill to its matching agent's
30
+ authoring conventions' effort mappings. Add `context: fork` with `agent:` only
31
+ for a skill the authoring conventions say to fork, and `background: false` when
32
+ its caller needs the result in the same turn. Add the skill to its matching agent's
32
33
  `skills:` list when that agent is project-owned; propose an upstream change for
33
34
  a package-owned agent instead of editing the installed copy. Verify bindings,
34
35
  relative resource links, executable steps, and final success criteria. Apply the
35
- 1000-line skill limit and other budgets from the authoring conventions, then
36
- return to the shared sync and verification steps.
36
+ word budgets from the authoring conventions, then return to the shared sync and
37
+ verification steps.
@@ -2,7 +2,6 @@
2
2
  name: intelligence-upgrade
3
3
  description: "Upgrade Intelligence and installed packages with migration checks"
4
4
  argument-hint: "[@scope/name]"
5
- agent: intelligence-operator
6
5
  disable-model-invocation: true
7
6
  ---
8
7