@ainova-systems/intelligence 0.11.0-rc.9 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/adapter.sh +46 -6
- package/cli/commands/init.sh +138 -33
- package/cli/commands/package.sh +2 -2
- package/cli/commands/registry.sh +3 -3
- package/cli/commands/status.sh +4 -4
- package/cli/commands/sync.sh +76 -23
- package/cli/commands/update.sh +1 -1
- package/cli/intelligence +18 -3
- package/cli/internal/{upgrade-v2.sh → align-project.sh} +9 -8
- package/cli/internal/check.sh +42 -2
- package/cli/internal/{migrate-v1.sh → convert-legacy.sh} +33 -29
- package/cli/internal/package-add.sh +1 -1
- package/cli/internal/package-list.sh +1 -1
- package/cli/internal/package-remove.sh +1 -1
- package/cli/internal/package-search.sh +1 -1
- package/cli/internal/package-update.sh +1 -1
- package/cli/internal/restore.sh +1 -1
- package/cli/internal/target-state.sh +26 -18
- package/cli/lib/adapter-lifecycle.sh +33 -0
- package/cli/lib/cli-common.sh +12 -8
- package/cli/lib/gitignore.sh +174 -0
- package/cli/lib/manifest.sh +35 -1
- package/cli/lib/onboarding.sh +63 -0
- package/engine/ENGINE_SHA +1 -1
- package/engine/adapters/_template.sh +19 -2
- package/engine/adapters/agents.sh +14 -3
- package/engine/adapters/claude.sh +15 -0
- package/engine/adapters/codex.sh +11 -1
- package/engine/adapters/copilot.sh +11 -0
- package/engine/adapters/cursor.sh +15 -0
- package/engine/adapters/opencode.sh +11 -0
- package/engine/adapters/pi.sh +18 -5
- package/engine/lib/adapter-contract.sh +100 -0
- package/engine/lib/common.sh +5 -0
- package/engine/lib/contract.sh +2 -2
- package/engine/sync.sh +87 -25
- package/package.json +1 -1
- package/packages/sync/agents/intelligence-architect.md +2 -2
- package/packages/sync/references/adapters.md +51 -7
- package/packages/sync/references/conventions.md +55 -20
- package/packages/sync/references/onboarding-migration.md +79 -0
- package/packages/sync/skills/intelligence-install-adapter/SKILL.md +12 -5
- package/packages/sync/skills/intelligence-learn-from-context/SKILL.md +23 -4
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +90 -38
- package/packages/sync/skills/intelligence-review-skills/SKILL.md +1 -1
- package/packages/sync/skills/intelligence-sync/SKILL.md +1 -1
package/cli/internal/check.sh
CHANGED
|
@@ -20,9 +20,9 @@ case "$IP_MODE" in
|
|
|
20
20
|
exit 0
|
|
21
21
|
;;
|
|
22
22
|
legacy)
|
|
23
|
-
echo "project:
|
|
23
|
+
echo "project: legacy Intelligence Sync at $IP_ROOT"
|
|
24
24
|
ok "vendored engine $(tr -d ' \t\r\n' < "$IP_MODULE_DIR/scripts/VERSION")"
|
|
25
|
-
echo " → 'intelligence init' converts it
|
|
25
|
+
echo " → 'intelligence init' converts it into an Intelligence project"
|
|
26
26
|
exit 0
|
|
27
27
|
;;
|
|
28
28
|
esac
|
|
@@ -86,6 +86,46 @@ while IFS= read -r name; do
|
|
|
86
86
|
fi
|
|
87
87
|
done < <(qmap_keys "$manifest" "packages")
|
|
88
88
|
|
|
89
|
+
# Adapter contracts are the shared source of truth for dependencies, rollback,
|
|
90
|
+
# onboarding backup and Git policy. Validate every declared target, including
|
|
91
|
+
# disabled ones, so a later enable cannot reveal a stale custom adapter.
|
|
92
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
93
|
+
while IFS= read -r target; do
|
|
94
|
+
[ -n "$target" ] || continue
|
|
95
|
+
output="$(get_target_output "$manifest" "$target")"
|
|
96
|
+
[ -n "$output" ] || output="$(default_target_output "$target")"
|
|
97
|
+
adapter_file="$(adapter_file_for "$IP_ROOT" "$content_dir" "$target" 2>/dev/null || true)"
|
|
98
|
+
if [ -z "$adapter_file" ]; then
|
|
99
|
+
warn "adapter '$target' is declared but its implementation is missing"
|
|
100
|
+
continue
|
|
101
|
+
fi
|
|
102
|
+
records="$(adapter_contract_records "$target" "$adapter_file" "$output" 2>/dev/null || true)"
|
|
103
|
+
if ! printf '%s\n' "$records" | grep -Fqx $'version\t1'; then
|
|
104
|
+
warn "adapter '$target' has an invalid ownership contract"
|
|
105
|
+
continue
|
|
106
|
+
fi
|
|
107
|
+
while IFS=$'\t' read -r kind value; do
|
|
108
|
+
if [ "$kind" = "requires" ] \
|
|
109
|
+
&& [ "$(is_target_enabled "$manifest" "$target")" = "1" ] \
|
|
110
|
+
&& [ "$(is_target_enabled "$manifest" "$value")" != "1" ]; then
|
|
111
|
+
warn "enabled adapter '$target' requires enabled adapter '$value'"
|
|
112
|
+
fi
|
|
113
|
+
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
114
|
+
case "$kind" in
|
|
115
|
+
ignore)
|
|
116
|
+
grep -Fqx -- "$value" "$IP_ROOT/.gitignore" 2>/dev/null \
|
|
117
|
+
|| warn "adapter '$target' Git policy is missing '$value'"
|
|
118
|
+
;;
|
|
119
|
+
include)
|
|
120
|
+
grep -Fqx -- "!$value" "$IP_ROOT/.gitignore" 2>/dev/null \
|
|
121
|
+
|| warn "adapter '$target' Git policy is missing '!$value'"
|
|
122
|
+
;;
|
|
123
|
+
esac
|
|
124
|
+
fi
|
|
125
|
+
done <<< "$records"
|
|
126
|
+
ok "adapter $target contract v1"
|
|
127
|
+
done < <(target_names "$manifest")
|
|
128
|
+
|
|
89
129
|
# Lock orphans.
|
|
90
130
|
if [ -f "$lock" ]; then
|
|
91
131
|
while IFS= read -r name; do
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Internal
|
|
3
|
-
# to
|
|
2
|
+
# Internal conversion from legacy Intelligence Sync to Intelligence CLI.
|
|
3
|
+
# to an Intelligence project: root intelligence.yaml, .intelligence/ store,
|
|
4
4
|
# intelligence.lock, no vendored engine.
|
|
5
5
|
#
|
|
6
6
|
# Transactional: STAGE everything next to the
|
|
@@ -24,8 +24,8 @@ done
|
|
|
24
24
|
detect_project
|
|
25
25
|
case "$IP_MODE" in
|
|
26
26
|
legacy) ;;
|
|
27
|
-
|
|
28
|
-
*) die "no
|
|
27
|
+
cli) die "already an Intelligence project ($IP_ROOT/intelligence.yaml)" ;;
|
|
28
|
+
*) die "no legacy Intelligence Sync project found here — 'intelligence init' starts a fresh project" ;;
|
|
29
29
|
esac
|
|
30
30
|
root="$IP_ROOT"
|
|
31
31
|
umbrella="$IP_UMBRELLA"
|
|
@@ -36,22 +36,22 @@ umbrella_rel="${umbrella#"$root"/}"
|
|
|
36
36
|
|
|
37
37
|
for existing in intelligence.yaml intelligence.lock .intelligence; do
|
|
38
38
|
if [ -e "$root/$existing" ] || [ -L "$root/$existing" ]; then
|
|
39
|
-
die "$existing already exists at the root — conflicting
|
|
39
|
+
die "$existing already exists at the root — conflicting Intelligence CLI state; move it aside or restore the project before converting"
|
|
40
40
|
fi
|
|
41
41
|
done
|
|
42
42
|
if [ "$dry_run" -eq 0 ] && [ "$force" -eq 0 ]; then
|
|
43
43
|
if [ -n "$(git -C "$root" status --porcelain 2>/dev/null)" ]; then
|
|
44
|
-
die "working tree is not clean — commit or stash first (or --force);
|
|
44
|
+
die "working tree is not clean — commit or stash first (or --force); conversion produces a one-commit diff you can review and revert"
|
|
45
45
|
fi
|
|
46
46
|
fi
|
|
47
47
|
|
|
48
|
-
# `sync_version` belongs to the
|
|
48
|
+
# `sync_version` belongs to the legacy Intelligence Sync format. Intelligence uses
|
|
49
49
|
# `schema_version`, so conversion reads the legacy scalar explicitly.
|
|
50
50
|
stamp="$(top_scalar "$config" "sync_version")"
|
|
51
51
|
eng="$(bundled_engine_version)"
|
|
52
52
|
[ -n "$stamp" ] && _ver_gt "$stamp" "$eng" && die "project schema $stamp is newer than this CLI's engine $eng — update the CLI first"
|
|
53
53
|
|
|
54
|
-
# The
|
|
54
|
+
# The legacy `packs:` block is the one Intelligence Sync shape conversion still reads, and the
|
|
55
55
|
# engine no longer knows it — the reader lives here, with its only consumer.
|
|
56
56
|
get_pack_field() {
|
|
57
57
|
get_nested_yaml_value "$1" "packs" "$2" "$3"
|
|
@@ -73,10 +73,10 @@ validate_mirror_path() {
|
|
|
73
73
|
case "$canon" in "$root"/*) ;; *) die "pack '$pack' mirror escapes the repository: '$mirror'" ;; esac
|
|
74
74
|
rel="${canon#"$root"/}"
|
|
75
75
|
|
|
76
|
-
# A
|
|
76
|
+
# A legacy mirror may live below the content directory (the normal location is
|
|
77
77
|
# intelligence/external/<pack>), but it must never own the directory itself,
|
|
78
78
|
# the vendored module, CLI state, authored sources, or generated outputs.
|
|
79
|
-
case "$umbrella_rel" in "$rel"|"$rel"/*) die "pack '$pack' mirror '$mirror' contains the
|
|
79
|
+
case "$umbrella_rel" in "$rel"|"$rel"/*) die "pack '$pack' mirror '$mirror' contains the legacy content directory" ;; esac
|
|
80
80
|
for protected in .git .intelligence "$module_rel"; do
|
|
81
81
|
paths_overlap "$rel" "$protected" && die "pack '$pack' mirror '$mirror' overlaps protected path '$protected'"
|
|
82
82
|
done
|
|
@@ -115,14 +115,14 @@ cleanup_stage() { rm -rf "$stage"; }
|
|
|
115
115
|
trap cleanup_stage EXIT INT TERM
|
|
116
116
|
mkdir -p "$stage/.intelligence/packages"
|
|
117
117
|
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# project already at the final
|
|
118
|
+
# Intelligence carries no legacy migration chain: the archived Intelligence Sync
|
|
119
|
+
# engine knows how to walk an old project forward. Conversion accepts only a
|
|
120
|
+
# project already at the final legacy schema and names the exact remedy otherwise —
|
|
121
121
|
# a remedy that keeps working indefinitely, because the archive stays at the
|
|
122
122
|
# URL the project's own update.sh already clones.
|
|
123
|
-
|
|
124
|
-
if [ -z "$stamp" ] || _ver_gt "$
|
|
125
|
-
echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final
|
|
123
|
+
LEGACY_FINAL_SCHEMA="0.10.0"
|
|
124
|
+
if [ -z "$stamp" ] || _ver_gt "$LEGACY_FINAL_SCHEMA" "$stamp"; then
|
|
125
|
+
echo "ERROR: project schema is ${stamp:-absent (pre-0.3.1)}, older than the final legacy Intelligence Sync schema $LEGACY_FINAL_SCHEMA." >&2
|
|
126
126
|
echo " Bring it forward with its own engine first — that still works:" >&2
|
|
127
127
|
echo " bash $module_rel/scripts/update.sh --yes" >&2
|
|
128
128
|
echo " then re-run: intelligence init" >&2
|
|
@@ -134,7 +134,7 @@ echo "== staging =="
|
|
|
134
134
|
# bundle (fetch_package's guard — no network).
|
|
135
135
|
sync_pkg_sha="$(fetch_package "$SYNC_PKG_URL" "v$eng" "$SYNC_PKG_PATH" "$stage/$SYNC_PKG_STORE")"
|
|
136
136
|
|
|
137
|
-
# Legacy pack name ->
|
|
137
|
+
# Legacy pack name -> Intelligence package name (@org/repo derived from the url), plus
|
|
138
138
|
# per-pack source facts. Recorded as rows for the config rewrite and the lock.
|
|
139
139
|
pack_rows="$stage/packs.rows"
|
|
140
140
|
: > "$pack_rows"
|
|
@@ -325,6 +325,10 @@ if [ -f "$root/.gitignore" ]; then
|
|
|
325
325
|
gitignore_existed=1
|
|
326
326
|
cp "$root/.gitignore" "$stage/gitignore.orig"
|
|
327
327
|
fi
|
|
328
|
+
while IFS= read -r policy_file; do
|
|
329
|
+
[ -f "$root/$policy_file" ] || continue
|
|
330
|
+
cp "$root/$policy_file" "$stage/$policy_file.orig"
|
|
331
|
+
done < <(publisher_ignore_file_names)
|
|
328
332
|
commit_active=1
|
|
329
333
|
rollback() {
|
|
330
334
|
[ "$commit_active" -eq 1 ] || return 0
|
|
@@ -336,6 +340,10 @@ rollback() {
|
|
|
336
340
|
else
|
|
337
341
|
rm -f "$root/.gitignore"
|
|
338
342
|
fi
|
|
343
|
+
while IFS= read -r policy_file; do
|
|
344
|
+
[ -f "$stage/$policy_file.orig" ] || continue
|
|
345
|
+
cp "$stage/$policy_file.orig" "$root/$policy_file"
|
|
346
|
+
done < <(publisher_ignore_file_names)
|
|
339
347
|
rm -rf "$stage"
|
|
340
348
|
echo "rolled back — the vendored setup is untouched" >&2
|
|
341
349
|
}
|
|
@@ -348,24 +356,19 @@ rollback_on_exit() {
|
|
|
348
356
|
# plain set -e/EXIT failure rather than only an explicit sync refusal.
|
|
349
357
|
trap rollback_on_exit EXIT
|
|
350
358
|
trap 'rollback; exit 130' INT TERM
|
|
351
|
-
if [ ! -f "$root/.gitignore" ] || ! grep -q '^\.intelligence/' "$root/.gitignore"; then
|
|
352
|
-
{
|
|
353
|
-
[ -f "$root/.gitignore" ] && [ -n "$(tail -c 1 "$root/.gitignore" 2>/dev/null)" ] && echo ""
|
|
354
|
-
echo "# intelligence CLI package store (restored automatically by 'intelligence sync')"
|
|
355
|
-
echo ".intelligence/"
|
|
356
|
-
} >> "$root/.gitignore"
|
|
357
|
-
fi
|
|
358
359
|
mv "$stage/.intelligence" "$root/.intelligence"
|
|
359
360
|
cp "$manifest_stage" "$root/intelligence.yaml"
|
|
360
361
|
lock_write_from_tsv "$root/intelligence.lock" "$lock_rows"
|
|
362
|
+
ensure_manifest_gitignore "$root" "$root/intelligence.yaml"
|
|
363
|
+
ensure_manifest_publisher_ignores "$root" "$root/intelligence.yaml"
|
|
361
364
|
|
|
362
365
|
sync_out="$(cd "$root" && IS_SUPPRESS_CLI_NOTE=1 bash "$CLI_DIR/commands/sync.sh" 2>&1)" || {
|
|
363
366
|
echo "$sync_out"
|
|
364
|
-
die "sync of the
|
|
367
|
+
die "sync of the converted state failed"
|
|
365
368
|
}
|
|
366
369
|
echo "$sync_out" | grep -q '^IS_STATUS=ok' || {
|
|
367
370
|
echo "$sync_out"
|
|
368
|
-
die "sync of the
|
|
371
|
+
die "sync of the converted state did not report ok"
|
|
369
372
|
}
|
|
370
373
|
# Point of no return: the new state is verified. From here an interrupt must
|
|
371
374
|
# NOT roll back (the old setup is being removed) — only clean the stage.
|
|
@@ -384,7 +387,7 @@ while IFS="$LOCK_SEP" read -r pack name url ref mirror; do
|
|
|
384
387
|
esac
|
|
385
388
|
if [ -d "$root/$mirror" ]; then
|
|
386
389
|
rm -rf "${root:?}/$mirror"
|
|
387
|
-
# An emptied
|
|
390
|
+
# An emptied legacy mirror parent (for example intelligence/external/) goes with it.
|
|
388
391
|
rmdir "$(dirname "$root/$mirror")" 2>/dev/null || true
|
|
389
392
|
fi
|
|
390
393
|
done < "$pack_rows"
|
|
@@ -395,6 +398,7 @@ rm -f "$config"
|
|
|
395
398
|
rmdir "$umbrella" 2>/dev/null || true
|
|
396
399
|
|
|
397
400
|
echo ""
|
|
398
|
-
echo "
|
|
401
|
+
echo "converted. Review the diff, then commit. The old config.yaml is kept at .intelligence/backup/config.yaml."
|
|
399
402
|
echo "From now on: intelligence sync | package | update | status."
|
|
400
|
-
|
|
403
|
+
report_tracked_managed_ignores "$root" "$root/intelligence.yaml"
|
|
404
|
+
echo "Next: ask your agent to run /intelligence-learn-from-repository to verify setup and review the converted project context."
|
|
@@ -12,7 +12,7 @@ term="${1:-}"
|
|
|
12
12
|
|
|
13
13
|
detect_project
|
|
14
14
|
manifest=""
|
|
15
|
-
[ "$IP_MODE" = "
|
|
15
|
+
[ "$IP_MODE" = "cli" ] && manifest="$IP_ROOT/intelligence.yaml"
|
|
16
16
|
|
|
17
17
|
# State of one package name in this project: installed / declared / available.
|
|
18
18
|
pkg_state() {
|
package/cli/internal/restore.sh
CHANGED
|
@@ -14,7 +14,7 @@ case "$action" in enable|disable) ;; *) die "internal target state: expected <en
|
|
|
14
14
|
[ -n "$name" ] || die "internal target state: missing adapter name"
|
|
15
15
|
assert_valid_target_name "$name"
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
require_cli_project
|
|
18
18
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
19
19
|
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
20
20
|
|
|
@@ -25,31 +25,39 @@ if [ "$action" = "enable" ]; then
|
|
|
25
25
|
die "adapter '$name' not found — create it first: intelligence adapter create $name"
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
-
# These adapters intentionally omit always-on rules because their tools
|
|
29
|
-
# read AGENTS.md. Do not create a manifest that full sync must refuse.
|
|
30
|
-
case "$name" in
|
|
31
|
-
cursor|copilot|codex|pi|opencode)
|
|
32
|
-
if [ "$(is_target_enabled "$manifest" "agents")" != "1" ]; then
|
|
33
|
-
die "target '$name' requires target 'agents' — enable it first"
|
|
34
|
-
fi
|
|
35
|
-
;;
|
|
36
|
-
esac
|
|
37
|
-
|
|
38
28
|
output="$(get_target_output "$manifest" "$name")"
|
|
39
29
|
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
30
|
+
validate_adapter_contract_for "$IP_ROOT" "$content_dir" "$name" "$output" \
|
|
31
|
+
|| die "adapter '$name' has an invalid ownership contract"
|
|
32
|
+
while IFS= read -r required; do
|
|
33
|
+
[ -n "$required" ] || continue
|
|
34
|
+
if [ "$(is_target_enabled "$manifest" "$required")" != "1" ]; then
|
|
35
|
+
die "target '$name' requires target '$required' — enable it first"
|
|
36
|
+
fi
|
|
37
|
+
done < <(adapter_records_for "$IP_ROOT" "$content_dir" "$name" "$output" \
|
|
38
|
+
| awk -F '\t' '$1 == "requires" { print $2 }')
|
|
40
39
|
target_set_enabled "$manifest" "$name" true "$output"
|
|
40
|
+
ensure_target_gitignore "$IP_ROOT" "$manifest" "$name"
|
|
41
|
+
ensure_manifest_publisher_ignores "$IP_ROOT" "$manifest"
|
|
41
42
|
echo "enabled: $name (output: $output)"
|
|
42
43
|
exit 0
|
|
43
44
|
fi
|
|
44
45
|
|
|
45
46
|
target_exists "$manifest" "$name" || die "target '$name' is not in the manifest"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
while IFS= read -r dependent; do
|
|
48
|
+
[ -n "$dependent" ] || continue
|
|
49
|
+
[ "$dependent" = "$name" ] && continue
|
|
50
|
+
[ "$(is_target_enabled "$manifest" "$dependent")" = "1" ] || continue
|
|
51
|
+
dependent_output="$(get_target_output "$manifest" "$dependent")"
|
|
52
|
+
[ -n "$dependent_output" ] || dependent_output="$(default_target_output "$dependent")"
|
|
53
|
+
dependent_records="$(adapter_records_for "$IP_ROOT" "$content_dir" "$dependent" "$dependent_output")" \
|
|
54
|
+
|| die "adapter '$dependent' has an invalid ownership contract"
|
|
55
|
+
if awk -F '\t' -v required="$name" \
|
|
56
|
+
'$1 == "requires" && $2 == required { found=1 } END { exit(found ? 0 : 1) }' \
|
|
57
|
+
<<< "$dependent_records"; then
|
|
58
|
+
die "target '$name' is required by enabled target '$dependent' — disable '$dependent' first"
|
|
59
|
+
fi
|
|
60
|
+
done < <(target_names "$manifest")
|
|
53
61
|
output="$(get_target_output "$manifest" "$name")"
|
|
54
62
|
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
55
63
|
target_set_enabled "$manifest" "$name" false "$output"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# CLI consumers of the engine's declarative adapter contract.
|
|
3
|
+
|
|
4
|
+
adapter_file_for() {
|
|
5
|
+
local root="$1" content_dir="$2" name="$3"
|
|
6
|
+
if [ -f "$root/$content_dir/adapters/$name.sh" ]; then
|
|
7
|
+
printf '%s\n' "$root/$content_dir/adapters/$name.sh"
|
|
8
|
+
elif [ -f "$IS_ENGINE_DIR/adapters/$name.sh" ]; then
|
|
9
|
+
printf '%s\n' "$IS_ENGINE_DIR/adapters/$name.sh"
|
|
10
|
+
else
|
|
11
|
+
return 1
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
adapter_records_for() {
|
|
16
|
+
local root="$1" content_dir="$2" name="$3" output="$4" file
|
|
17
|
+
file="$(adapter_file_for "$root" "$content_dir" "$name")" || {
|
|
18
|
+
echo "ERROR: adapter '$name' not found" >&2
|
|
19
|
+
return 1
|
|
20
|
+
}
|
|
21
|
+
adapter_contract_records "$name" "$file" "$output"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validate_adapter_contract_for() {
|
|
25
|
+
local root="$1" content_dir="$2" name="$3" output="$4"
|
|
26
|
+
adapter_records_for "$root" "$content_dir" "$name" "$output" >/dev/null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
adapter_contract_paths() {
|
|
30
|
+
local root="$1" content_dir="$2" name="$3" output="$4"
|
|
31
|
+
adapter_records_for "$root" "$content_dir" "$name" "$output" \
|
|
32
|
+
| awk -F '\t' '$1 == "owned" || $1 == "managed" || $1 == "legacy" || $1 == "preserve" { print $2 }'
|
|
33
|
+
}
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# IS_ENGINE_DIR come exported from the dispatcher.
|
|
12
12
|
source "$IS_ENGINE_DIR/lib/common.sh"
|
|
13
13
|
source "$IS_ENGINE_DIR/lib/contract.sh"
|
|
14
|
+
source "$IS_ENGINE_DIR/lib/adapter-contract.sh"
|
|
14
15
|
|
|
15
16
|
die() {
|
|
16
17
|
echo "ERROR: $*" >&2
|
|
@@ -50,6 +51,9 @@ source "$CLI_DIR/lib/manifest.sh"
|
|
|
50
51
|
source "$CLI_DIR/lib/semver.sh"
|
|
51
52
|
source "$CLI_DIR/lib/registry.sh"
|
|
52
53
|
source "$CLI_DIR/lib/lockfile.sh"
|
|
54
|
+
source "$CLI_DIR/lib/adapter-lifecycle.sh"
|
|
55
|
+
source "$CLI_DIR/lib/gitignore.sh"
|
|
56
|
+
source "$CLI_DIR/lib/onboarding.sh"
|
|
53
57
|
|
|
54
58
|
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
55
59
|
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
@@ -70,11 +74,11 @@ DEFAULT_REGISTRY_URL="$(top_scalar "$_EPKG" "default_registry")"
|
|
|
70
74
|
SYNC_PKG_STORE=".intelligence/packages/$SYNC_PKG_NAME"
|
|
71
75
|
|
|
72
76
|
# --- Project detection ---------------------------------------------------
|
|
73
|
-
# Sets: IP_MODE (
|
|
77
|
+
# Sets: IP_MODE (cli|legacy|none), IP_ROOT, IP_UMBRELLA, IP_MODULE_DIR.
|
|
74
78
|
# These ARE this lib's public API — every command reads them after calling
|
|
75
79
|
# detect_project, which per-file shellcheck cannot see.
|
|
76
80
|
# shellcheck disable=SC2034
|
|
77
|
-
#
|
|
81
|
+
# The CLI product wins: the nearest ancestor holding intelligence.yaml. Legacy is a git
|
|
78
82
|
# repo holding an umbrella (a dir with config.yaml) whose module is identified
|
|
79
83
|
# by role — scripts/sync.sh + scripts/VERSION — never by name.
|
|
80
84
|
detect_project() {
|
|
@@ -82,7 +86,7 @@ detect_project() {
|
|
|
82
86
|
local dir="$PWD"
|
|
83
87
|
while :; do
|
|
84
88
|
if [ -f "$dir/intelligence.yaml" ]; then
|
|
85
|
-
IP_MODE="
|
|
89
|
+
IP_MODE="cli"
|
|
86
90
|
IP_ROOT="$(cd "$dir" && pwd)"
|
|
87
91
|
return 0
|
|
88
92
|
fi
|
|
@@ -109,11 +113,11 @@ detect_project() {
|
|
|
109
113
|
return 0
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
require_cli_project() {
|
|
113
117
|
detect_project
|
|
114
118
|
case "$IP_MODE" in
|
|
115
|
-
|
|
116
|
-
legacy) die "this is a
|
|
119
|
+
cli) ;;
|
|
120
|
+
legacy) die "this is a legacy Intelligence Sync project — run 'intelligence init' to convert it, or keep using its own flow" ;;
|
|
117
121
|
*) die "no intelligence project found here — run 'intelligence init'" ;;
|
|
118
122
|
esac
|
|
119
123
|
}
|
|
@@ -176,7 +180,7 @@ export_engine_env() {
|
|
|
176
180
|
|
|
177
181
|
# --- Project lifecycle preflight -----------------------------------------
|
|
178
182
|
# Public commands are intentionally few. They share this state gate so a CLI
|
|
179
|
-
# installed at a newer engine version brings the current
|
|
183
|
+
# installed at a newer engine version brings the current Intelligence project forward
|
|
180
184
|
# before a mutating operation. The npm install itself cannot do that: it runs
|
|
181
185
|
# outside any project and does not know which repositories the user owns.
|
|
182
186
|
|
|
@@ -268,7 +272,7 @@ ensure_project_current() {
|
|
|
268
272
|
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
269
273
|
fi
|
|
270
274
|
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
271
|
-
bash "$CLI_DIR/internal/
|
|
275
|
+
bash "$CLI_DIR/internal/align-project.sh" --no-sync
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
project_store_missing() {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generated-output policy derived from each adapter's ownership contract.
|
|
3
|
+
|
|
4
|
+
gitignore_add_line() {
|
|
5
|
+
local root="$1" line="$2" file="$1/.gitignore"
|
|
6
|
+
if [ -f "$file" ] && grep -Fqx -- "$line" "$file"; then
|
|
7
|
+
return 0
|
|
8
|
+
fi
|
|
9
|
+
printf '%s\n' "$line" >> "$file"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
ensure_gitignore_header() {
|
|
13
|
+
local root="$1" file="$1/.gitignore"
|
|
14
|
+
if [ ! -f "$file" ] || ! grep -Fqx '# Intelligence generated state and tool output' "$file"; then
|
|
15
|
+
if [ -f "$file" ] && [ -s "$file" ] && [ -n "$(tail -c 1 "$file" 2>/dev/null)" ]; then
|
|
16
|
+
printf '\n' >> "$file"
|
|
17
|
+
fi
|
|
18
|
+
printf '%s\n' '# Intelligence generated state and tool output' >> "$file"
|
|
19
|
+
fi
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ensure_base_gitignore() {
|
|
23
|
+
local root="$1"
|
|
24
|
+
ensure_gitignore_header "$root"
|
|
25
|
+
gitignore_add_line "$root" '.intelligence/'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ensure_target_gitignore() {
|
|
29
|
+
local root="$1" manifest="$2" target="$3" content_dir output kind value records
|
|
30
|
+
ensure_gitignore_header "$root"
|
|
31
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
32
|
+
output="$(get_target_output "$manifest" "$target")"
|
|
33
|
+
[ -n "$output" ] || output="$(default_target_output "$target")"
|
|
34
|
+
records="$(adapter_records_for "$root" "$content_dir" "$target" "$output")" \
|
|
35
|
+
|| die "adapter '$target' has an invalid ownership contract"
|
|
36
|
+
while IFS=$'\t' read -r kind value; do
|
|
37
|
+
case "$kind" in
|
|
38
|
+
ignore) gitignore_add_line "$root" "$value" ;;
|
|
39
|
+
include) gitignore_add_line "$root" "!$value" ;;
|
|
40
|
+
esac
|
|
41
|
+
done <<< "$records"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ensure_manifest_gitignore() {
|
|
45
|
+
local root="$1" manifest="$2" target
|
|
46
|
+
ensure_base_gitignore "$root"
|
|
47
|
+
while IFS= read -r target; do
|
|
48
|
+
[ -n "$target" ] || continue
|
|
49
|
+
if [ "$(is_target_enabled "$manifest" "$target")" = "1" ]; then
|
|
50
|
+
ensure_target_gitignore "$root" "$manifest" "$target"
|
|
51
|
+
fi
|
|
52
|
+
done < <(target_names "$manifest")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Publishing/build tools do not share Git's ignore policy. In particular,
|
|
56
|
+
# .vscodeignore and .npmignore become the packager's own filter, while Docker
|
|
57
|
+
# always builds from its independent context filter. Only amend files the
|
|
58
|
+
# project already has: their presence is the explicit signal that this root is
|
|
59
|
+
# packaged or sent as a build context.
|
|
60
|
+
publisher_ignore_file_names() {
|
|
61
|
+
printf '%s\n' .vscodeignore .npmignore .dockerignore
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
publisher_ignore_add_line() {
|
|
65
|
+
local file="$1" line="$2"
|
|
66
|
+
if grep -Fqx -- "$line" "$file" 2>/dev/null; then
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
printf '%s\n' "$line" >> "$file"
|
|
70
|
+
PUBLISH_IGNORE_FILE_CHANGED=1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
publisher_ignore_add_path() {
|
|
74
|
+
local file="$1" path="${2#./}"
|
|
75
|
+
path="${path%/}"
|
|
76
|
+
[ -n "$path" ] || return 0
|
|
77
|
+
publisher_ignore_add_line "$file" "$path"
|
|
78
|
+
publisher_ignore_add_line "$file" "$path/**"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ensure_manifest_publisher_ignores() {
|
|
82
|
+
local root="$1" manifest="$2" rel file content_dir target output records kind value
|
|
83
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
84
|
+
while IFS= read -r rel; do
|
|
85
|
+
[ -n "$rel" ] || continue
|
|
86
|
+
file="$root/$rel"
|
|
87
|
+
[ -f "$file" ] || continue
|
|
88
|
+
PUBLISH_IGNORE_FILE_CHANGED=0
|
|
89
|
+
if ! grep -Fqx '# Intelligence development context and generated output' "$file" 2>/dev/null; then
|
|
90
|
+
if [ -s "$file" ] && [ -n "$(tail -c 1 "$file" 2>/dev/null)" ]; then
|
|
91
|
+
printf '\n' >> "$file"
|
|
92
|
+
fi
|
|
93
|
+
printf '%s\n' '# Intelligence development context and generated output' >> "$file"
|
|
94
|
+
PUBLISH_IGNORE_FILE_CHANGED=1
|
|
95
|
+
fi
|
|
96
|
+
publisher_ignore_add_path "$file" '.intelligence'
|
|
97
|
+
publisher_ignore_add_line "$file" 'intelligence.yaml'
|
|
98
|
+
publisher_ignore_add_line "$file" 'intelligence.lock'
|
|
99
|
+
publisher_ignore_add_path "$file" "$content_dir"
|
|
100
|
+
while IFS= read -r target; do
|
|
101
|
+
[ -n "$target" ] || continue
|
|
102
|
+
[ "$(is_target_enabled "$manifest" "$target")" = "1" ] || continue
|
|
103
|
+
output="$(get_target_output "$manifest" "$target")"
|
|
104
|
+
[ -n "$output" ] || output="$(default_target_output "$target")"
|
|
105
|
+
publisher_ignore_add_path "$file" "$output"
|
|
106
|
+
records="$(adapter_records_for "$root" "$content_dir" "$target" "$output")" \
|
|
107
|
+
|| die "adapter '$target' has an invalid ownership contract"
|
|
108
|
+
while IFS=$'\t' read -r kind value; do
|
|
109
|
+
case "$kind" in
|
|
110
|
+
owned|managed|legacy) publisher_ignore_add_path "$file" "$value" ;;
|
|
111
|
+
esac
|
|
112
|
+
done <<< "$records"
|
|
113
|
+
done < <(target_names "$manifest")
|
|
114
|
+
if [ "$PUBLISH_IGNORE_FILE_CHANGED" -eq 1 ]; then
|
|
115
|
+
echo "packaging exclusions updated: $rel"
|
|
116
|
+
fi
|
|
117
|
+
done < <(publisher_ignore_file_names)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
managed_gitignore_patterns() {
|
|
121
|
+
local root="$1" manifest="$2" content_dir target output records kind value
|
|
122
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
123
|
+
printf '%s\n' '.intelligence/' "$content_dir/_backup/"
|
|
124
|
+
while IFS= read -r target; do
|
|
125
|
+
[ -n "$target" ] || continue
|
|
126
|
+
[ "$(is_target_enabled "$manifest" "$target")" = "1" ] || continue
|
|
127
|
+
output="$(get_target_output "$manifest" "$target")"
|
|
128
|
+
[ -n "$output" ] || output="$(default_target_output "$target")"
|
|
129
|
+
records="$(adapter_records_for "$root" "$content_dir" "$target" "$output")" \
|
|
130
|
+
|| return 1
|
|
131
|
+
while IFS=$'\t' read -r kind value; do
|
|
132
|
+
case "$kind" in
|
|
133
|
+
ignore) printf '%s\n' "$value" ;;
|
|
134
|
+
include) printf '!%s\n' "$value" ;;
|
|
135
|
+
esac
|
|
136
|
+
done <<< "$records"
|
|
137
|
+
done < <(target_names "$manifest")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Git does not apply ignore rules retroactively to files already in its index.
|
|
141
|
+
# Name each affected path and print a command which only untracks it; the local
|
|
142
|
+
# file remains available as migration input.
|
|
143
|
+
report_tracked_managed_ignores() {
|
|
144
|
+
local root="$1" manifest="$2" patterns tracked path quoted posix_quoted powershell_quoted
|
|
145
|
+
git -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
|
|
146
|
+
patterns="$(mktemp -t intelligence-gitignore-XXXXXX)"
|
|
147
|
+
tracked="$(mktemp -t intelligence-tracked-XXXXXX)"
|
|
148
|
+
managed_gitignore_patterns "$root" "$manifest" > "$patterns" || {
|
|
149
|
+
rm -f "$patterns" "$tracked"
|
|
150
|
+
return 0
|
|
151
|
+
}
|
|
152
|
+
git -C "$root" ls-files -z -ci --exclude-from="$patterns" > "$tracked" 2>/dev/null || true
|
|
153
|
+
rm -f "$patterns"
|
|
154
|
+
[ -s "$tracked" ] || {
|
|
155
|
+
rm -f "$tracked"
|
|
156
|
+
return 0
|
|
157
|
+
}
|
|
158
|
+
echo " Tracked files still bypass these .gitignore rules. Untrack them without deleting local copies:"
|
|
159
|
+
while IFS= read -r -d '' path; do
|
|
160
|
+
[ -n "$path" ] || continue
|
|
161
|
+
if [[ "$path" == *"'"* ]]; then
|
|
162
|
+
# macOS still ships Bash 3.2; use portable sed rather than newer
|
|
163
|
+
# parameter-replacement behavior for the embedded quote.
|
|
164
|
+
posix_quoted="$(printf '%s' "$path" | sed "s/'/'\\\\''/g")"
|
|
165
|
+
powershell_quoted="$(printf '%s' "$path" | sed "s/'/''/g")"
|
|
166
|
+
printf " POSIX: git rm --cached -- '%s'\n" "$posix_quoted"
|
|
167
|
+
printf " PowerShell: git rm --cached -- '%s'\n" "$powershell_quoted"
|
|
168
|
+
else
|
|
169
|
+
quoted="'$path'"
|
|
170
|
+
printf ' git rm --cached -- %s\n' "$quoted"
|
|
171
|
+
fi
|
|
172
|
+
done < "$tracked"
|
|
173
|
+
rm -f "$tracked"
|
|
174
|
+
}
|