@ainova-systems/intelligence 0.11.0-rc.6 → 0.11.0-rc.8
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/README.md +15 -16
- package/cli/commands/adapter.sh +153 -0
- package/cli/commands/init.sh +113 -22
- package/cli/commands/package.sh +30 -0
- package/cli/commands/registry.sh +5 -2
- package/cli/commands/status.sh +12 -4
- package/cli/commands/sync.sh +8 -21
- package/cli/commands/update.sh +76 -61
- package/cli/engine-package.yaml +4 -4
- package/cli/intelligence +37 -27
- package/cli/{commands/doctor.sh → internal/check.sh} +32 -14
- package/cli/{commands/migrate.sh → internal/migrate-v1.sh} +161 -42
- package/cli/{commands/add.sh → internal/package-add.sh} +11 -10
- package/cli/{commands/list.sh → internal/package-list.sh} +9 -4
- package/cli/{commands/remove.sh → internal/package-remove.sh} +3 -3
- package/cli/{commands/search.sh → internal/package-search.sh} +4 -4
- package/cli/internal/package-update.sh +97 -0
- package/cli/{commands/install.sh → internal/restore.sh} +11 -4
- package/cli/internal/target-state.sh +57 -0
- package/cli/{commands/upgrade.sh → internal/upgrade-v2.sh} +48 -8
- package/cli/lib/cli-common.sh +167 -14
- package/cli/lib/lockfile.sh +15 -9
- package/cli/lib/manifest.sh +106 -1
- package/cli/lib/registry.sh +29 -15
- package/cli/lib/semver.sh +4 -2
- package/engine/ENGINE_SHA +1 -0
- package/engine/{scripts/adapters → adapters}/_template.sh +10 -9
- package/engine/{scripts/adapters → adapters}/agents.sh +20 -25
- package/engine/{scripts/adapters → adapters}/opencode.sh +1 -1
- package/engine/{scripts/lib → lib}/common.sh +29 -541
- package/engine/lib/contract.sh +120 -0
- package/engine/sync.sh +238 -0
- package/package.json +6 -5
- package/{engine → packages/sync}/agents/intelligence-architect.md +5 -3
- package/{engine → packages/sync}/agents/intelligence-operator.md +10 -13
- package/packages/sync/references/adapters.md +252 -0
- package/packages/sync/references/conventions.md +385 -0
- package/{engine → packages/sync}/rules/intelligence-authoring.md +6 -6
- package/{engine → packages/sync}/skills/intelligence-add-agent/SKILL.md +5 -5
- package/{engine → packages/sync}/skills/intelligence-add-rule/SKILL.md +3 -3
- package/{engine → packages/sync}/skills/intelligence-add-skill/SKILL.md +3 -3
- package/{engine → packages/sync}/skills/intelligence-extract-skill/SKILL.md +2 -2
- package/packages/sync/skills/intelligence-install-adapter/SKILL.md +38 -0
- package/{engine → packages/sync}/skills/intelligence-learn-from-context/SKILL.md +3 -3
- package/packages/sync/skills/intelligence-learn-from-repository/SKILL.md +55 -0
- package/{engine → packages/sync}/skills/intelligence-review-skills/SKILL.md +6 -6
- package/packages/sync/skills/intelligence-sync/SKILL.md +20 -0
- package/packages/sync/skills/intelligence-uninstall-adapter/SKILL.md +24 -0
- package/packages/sync/skills/intelligence-update/SKILL.md +43 -0
- package/engine/INIT.md +0 -500
- package/engine/docs/ADAPTERS.md +0 -214
- package/engine/docs/CLI.md +0 -90
- package/engine/docs/CONVENTIONS.md +0 -456
- package/engine/scripts/ENGINE_SHA +0 -1
- package/engine/scripts/lib/layout.sh +0 -51
- package/engine/scripts/lib/migrations.sh +0 -708
- package/engine/scripts/sync.sh +0 -311
- package/engine/scripts/update.sh +0 -237
- package/engine/skills/intelligence-install-adapter/SKILL.md +0 -31
- package/engine/skills/intelligence-sync/SKILL.md +0 -16
- package/engine/skills/intelligence-uninstall-adapter/SKILL.md +0 -42
- package/engine/skills/intelligence-update/SKILL.md +0 -165
- /package/engine/{scripts/VERSION → VERSION} +0 -0
- /package/engine/{scripts/adapters → adapters}/claude.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/codex.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/copilot.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/cursor.sh +0 -0
- /package/engine/{scripts/adapters → adapters}/pi.sh +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Internal adapter target-state operation.
|
|
3
|
+
#
|
|
4
|
+
# Change only the manifest. Syncing and deleting generated output are separate
|
|
5
|
+
# explicit actions: a generic target command cannot infer a custom adapter's
|
|
6
|
+
# ownership safely.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
source "$CLI_DIR/lib/cli-common.sh"
|
|
9
|
+
|
|
10
|
+
action="${1:-}"
|
|
11
|
+
name="${2:-}"
|
|
12
|
+
[ $# -le 2 ] || die "internal target state: expected <enable|disable> <name>"
|
|
13
|
+
case "$action" in enable|disable) ;; *) die "internal target state: expected <enable|disable> <name>" ;; esac
|
|
14
|
+
[ -n "$name" ] || die "internal target state: missing adapter name"
|
|
15
|
+
assert_valid_target_name "$name"
|
|
16
|
+
|
|
17
|
+
require_v2
|
|
18
|
+
manifest="$IP_ROOT/intelligence.yaml"
|
|
19
|
+
content_dir="$(manifest_intelligence_dir "$manifest")"
|
|
20
|
+
|
|
21
|
+
if [ "$action" = "enable" ]; then
|
|
22
|
+
project_adapter="$IP_ROOT/$content_dir/adapters/$name.sh"
|
|
23
|
+
bundled_adapter="$IS_ENGINE_DIR/adapters/$name.sh"
|
|
24
|
+
if [ ! -f "$project_adapter" ] && [ ! -f "$bundled_adapter" ]; then
|
|
25
|
+
die "adapter '$name' not found — create it first: intelligence adapter create $name"
|
|
26
|
+
fi
|
|
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
|
+
output="$(get_target_output "$manifest" "$name")"
|
|
39
|
+
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
40
|
+
target_set_enabled "$manifest" "$name" true "$output"
|
|
41
|
+
echo "enabled: $name (output: $output)"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
target_exists "$manifest" "$name" || die "target '$name' is not in the manifest"
|
|
46
|
+
if [ "$name" = "agents" ]; then
|
|
47
|
+
for dependent in cursor copilot codex pi opencode; do
|
|
48
|
+
if [ "$(is_target_enabled "$manifest" "$dependent")" = "1" ]; then
|
|
49
|
+
die "target 'agents' is required by enabled target '$dependent' — disable '$dependent' first"
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
52
|
+
fi
|
|
53
|
+
output="$(get_target_output "$manifest" "$name")"
|
|
54
|
+
[ -n "$output" ] || output="$(default_target_output "$name")"
|
|
55
|
+
target_set_enabled "$manifest" "$name" false "$output"
|
|
56
|
+
echo "disabled: $name"
|
|
57
|
+
echo " generated output was kept; remove only paths owned by this adapter"
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# Internal: bring the project to this CLI's engine: apply v2
|
|
3
3
|
# schema migrations, align the engine-content package to the bundled version,
|
|
4
|
-
# restamp
|
|
4
|
+
# restamp schema_version, sync.
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
source "$CLI_DIR/lib/cli-common.sh"
|
|
7
7
|
|
|
8
|
+
no_sync=0
|
|
9
|
+
while [ $# -gt 0 ]; do
|
|
10
|
+
case "$1" in
|
|
11
|
+
--no-sync) no_sync=1 ;;
|
|
12
|
+
*) die "internal upgrade: unknown option '$1'" ;;
|
|
13
|
+
esac
|
|
14
|
+
shift
|
|
15
|
+
done
|
|
16
|
+
|
|
8
17
|
require_v2
|
|
9
18
|
manifest="$IP_ROOT/intelligence.yaml"
|
|
10
19
|
|
|
11
|
-
stamp="$(
|
|
20
|
+
stamp="$(read_schema_version "$manifest")"
|
|
21
|
+
legacy_stamp="$(top_scalar "$manifest" "sync_version")"
|
|
22
|
+
schema_key_count="$(awk '/^schema_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
|
|
23
|
+
legacy_key_count="$(awk '/^sync_version:[[:space:]]*/ { n++ } END { print n + 0 }' "$manifest")"
|
|
12
24
|
eng="$(bundled_engine_version)"
|
|
25
|
+
[ "$schema_key_count" -le 1 ] || die "manifest has duplicate schema_version keys"
|
|
26
|
+
[ "$legacy_key_count" -le 1 ] || die "manifest has duplicate sync_version keys"
|
|
13
27
|
if [ -n "$stamp" ] && _ver_gt "$stamp" "$eng"; then
|
|
14
|
-
die "manifest schema $stamp is newer than this CLI's engine $eng — update the CLI first
|
|
28
|
+
die "manifest schema $stamp is newer than this CLI's engine $eng — update the global CLI first"
|
|
29
|
+
fi
|
|
30
|
+
if [ -n "$legacy_stamp" ] && _ver_gt "$legacy_stamp" "$eng"; then
|
|
31
|
+
die "manifest schema $legacy_stamp is newer than this CLI's engine $eng — update the global CLI first"
|
|
15
32
|
fi
|
|
16
33
|
|
|
17
34
|
# --- v2 schema migrations -------------------------------------------------
|
|
@@ -19,6 +36,19 @@ fi
|
|
|
19
36
|
# Same discipline: ascending, append-only, each migration self-detects and
|
|
20
37
|
# no-ops when already applied.
|
|
21
38
|
|
|
39
|
+
# v2 migration 0: the original RC manifest called the permanent schema key
|
|
40
|
+
# `sync_version`. Rename it without changing its value; the final stamp below
|
|
41
|
+
# then aligns it with this engine. The archived v1 reader remains separate.
|
|
42
|
+
if [ -n "$legacy_stamp" ]; then
|
|
43
|
+
if [ -n "$stamp" ]; then
|
|
44
|
+
[ "$stamp" = "$legacy_stamp" ] || die "manifest has conflicting schema_version '$stamp' and sync_version '$legacy_stamp'"
|
|
45
|
+
_qmap_stage "$manifest" '$0 !~ /^sync_version:[[:space:]]*/'
|
|
46
|
+
else
|
|
47
|
+
_qmap_stage "$manifest" '{ sub(/^sync_version:/, "schema_version:"); print }'
|
|
48
|
+
fi
|
|
49
|
+
echo " migrating: sync_version -> schema_version"
|
|
50
|
+
fi
|
|
51
|
+
|
|
22
52
|
# v2 migration 1: staged engine content -> the @ainova-systems/sync package.
|
|
23
53
|
# Pre-package manifests listed `.intelligence/engine/{rules,agents,skills}`
|
|
24
54
|
# as sources fed by a CLI-staged copy; that copy is now an ordinary package
|
|
@@ -59,10 +89,20 @@ if [ "$have_pkg" -eq 1 ] && [ "$migrated" -eq 0 ]; then
|
|
|
59
89
|
sync_pkg_install "$IP_ROOT"
|
|
60
90
|
fi
|
|
61
91
|
if [ "$have_pkg" -eq 0 ] && [ "$migrated" -eq 0 ]; then
|
|
62
|
-
echo " NOTE: $SYNC_PKG_NAME is not in this manifest (bare setup) — engine meta-skills stay uninstalled; 'intelligence add $SYNC_PKG_NAME' opts back in." >&2
|
|
92
|
+
echo " NOTE: $SYNC_PKG_NAME is not in this manifest (bare setup) — engine meta-skills stay uninstalled; 'intelligence package add $SYNC_PKG_NAME' opts back in." >&2
|
|
63
93
|
fi
|
|
64
94
|
|
|
65
|
-
|
|
66
|
-
|
|
95
|
+
# A previous RC could leave this managed directory even after its source
|
|
96
|
+
# entries were already removed. Its presence is itself an upgrade predicate,
|
|
97
|
+
# so always close that post-condition.
|
|
98
|
+
if [ -d "$IP_ROOT/.intelligence/engine" ]; then
|
|
99
|
+
rm -rf "$IP_ROOT/.intelligence/engine"
|
|
100
|
+
echo " removed stale .intelligence/engine"
|
|
101
|
+
fi
|
|
67
102
|
|
|
68
|
-
|
|
103
|
+
stamp_schema_version "$manifest" "$eng"
|
|
104
|
+
echo " schema_version -> $eng"
|
|
105
|
+
|
|
106
|
+
if [ "$no_sync" -eq 0 ]; then
|
|
107
|
+
exec bash "$CLI_DIR/commands/sync.sh"
|
|
108
|
+
fi
|
package/cli/lib/cli-common.sh
CHANGED
|
@@ -7,18 +7,45 @@
|
|
|
7
7
|
# CLI-owned parsing lives in lib/manifest.sh (quoted-key `packages:` /
|
|
8
8
|
# `registries:` blocks, which the engine deliberately never reads).
|
|
9
9
|
|
|
10
|
-
# Engine libraries (readers, is_status, IS_RC_*, engine_version).
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
source "$IS_ENGINE_DIR/
|
|
14
|
-
source "$IS_ENGINE_DIR/scripts/lib/layout.sh"
|
|
15
|
-
source "$IS_ENGINE_DIR/scripts/lib/migrations.sh"
|
|
10
|
+
# Engine libraries (readers, is_status, IS_RC_*, engine_version). CLI_DIR /
|
|
11
|
+
# IS_ENGINE_DIR come exported from the dispatcher.
|
|
12
|
+
source "$IS_ENGINE_DIR/lib/common.sh"
|
|
13
|
+
source "$IS_ENGINE_DIR/lib/contract.sh"
|
|
16
14
|
|
|
17
15
|
die() {
|
|
18
16
|
echo "ERROR: $*" >&2
|
|
19
17
|
exit 1
|
|
20
18
|
}
|
|
21
19
|
|
|
20
|
+
# --- Untrusted-input guards ----------------------------------------------
|
|
21
|
+
# Manifest, lock and registry indexes arrive with a cloned repo: every value
|
|
22
|
+
# they carry is attacker-adjacent. URLs and refs reach git argv (a leading
|
|
23
|
+
# `-` would be an option — `--upload-pack=<cmd>` is code execution), names
|
|
24
|
+
# become store paths fed to rm -rf. Guard at the choke points, loudly.
|
|
25
|
+
|
|
26
|
+
# assert_safe_source_url <url> — scheme allowlist (the engine's own list) or
|
|
27
|
+
# scp-like user@host:path; never option-shaped, never quote-bearing.
|
|
28
|
+
assert_safe_source_url() {
|
|
29
|
+
local url="$1"
|
|
30
|
+
case "$url" in
|
|
31
|
+
""|-*) die "unsafe source url '$url' — option-shaped or empty" ;;
|
|
32
|
+
*[\"\'\ ]*) die "unsafe source url '$url' — quotes or spaces" ;;
|
|
33
|
+
https://*|http://*|ssh://*|git://*|file://*) ;;
|
|
34
|
+
*@*:*) ;;
|
|
35
|
+
*) die "unsafe source url '$url' — allowed: https, http, ssh, git, file, or user@host:path" ;;
|
|
36
|
+
esac
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# assert_safe_ref <ref-or-empty> — tags/branches/SHAs; never option-shaped.
|
|
40
|
+
assert_safe_ref() {
|
|
41
|
+
local ref="$1"
|
|
42
|
+
[ -z "$ref" ] && return 0
|
|
43
|
+
case "$ref" in
|
|
44
|
+
-*) die "unsafe git ref '$ref' — option-shaped" ;;
|
|
45
|
+
*[\"\'\ \\]*) die "unsafe git ref '$ref'" ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
22
49
|
source "$CLI_DIR/lib/manifest.sh"
|
|
23
50
|
source "$CLI_DIR/lib/semver.sh"
|
|
24
51
|
source "$CLI_DIR/lib/registry.sh"
|
|
@@ -27,8 +54,8 @@ source "$CLI_DIR/lib/lockfile.sh"
|
|
|
27
54
|
# The engine-content package: OPTIONAL but auto-selected at init. Package by
|
|
28
55
|
# UX (manifest entry, lockfile row, list/search/remove), bundle by mechanics —
|
|
29
56
|
# at the version the CLI ships, it materializes from the npm bundle without
|
|
30
|
-
# network; only a cross-version
|
|
31
|
-
# at the bundled engine version and moved
|
|
57
|
+
# network; only a cross-version acquisition reaches git. The pin is held
|
|
58
|
+
# exactly at the bundled engine version and moved by lifecycle alignment.
|
|
32
59
|
#
|
|
33
60
|
# Its identity is DATA shipped with the distribution (cli/engine-package.yaml),
|
|
34
61
|
# never a name compiled into cli code — a fork edits the file.
|
|
@@ -86,7 +113,7 @@ require_v2() {
|
|
|
86
113
|
detect_project
|
|
87
114
|
case "$IP_MODE" in
|
|
88
115
|
v2) ;;
|
|
89
|
-
legacy) die "this is a vendored (v1) setup — run 'intelligence
|
|
116
|
+
legacy) die "this is a vendored (v1) setup — run 'intelligence init' to migrate it, or keep using its own flow" ;;
|
|
90
117
|
*) die "no intelligence project found here — run 'intelligence init'" ;;
|
|
91
118
|
esac
|
|
92
119
|
}
|
|
@@ -99,8 +126,16 @@ manifest_intelligence_dir() {
|
|
|
99
126
|
printf '%s' "${v:-intelligence}"
|
|
100
127
|
}
|
|
101
128
|
|
|
129
|
+
default_target_output() {
|
|
130
|
+
case "$1" in
|
|
131
|
+
agents) printf '%s' "AGENTS.md" ;;
|
|
132
|
+
copilot) printf '%s' ".github" ;;
|
|
133
|
+
*) printf '.%s' "$1" ;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
102
137
|
bundled_engine_version() {
|
|
103
|
-
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/
|
|
138
|
+
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/VERSION"
|
|
104
139
|
}
|
|
105
140
|
|
|
106
141
|
# --- The sync package's manifest/lock plumbing ---------------------------
|
|
@@ -129,14 +164,132 @@ sync_pkg_install() {
|
|
|
129
164
|
# Everything the IS_CLI mode of sync.sh needs, derived from the manifest.
|
|
130
165
|
export_engine_env() {
|
|
131
166
|
local root="$1"
|
|
132
|
-
local
|
|
133
|
-
|
|
167
|
+
local content_rel
|
|
168
|
+
content_rel="$(manifest_intelligence_dir "$root/intelligence.yaml")"
|
|
134
169
|
export IS_CLI=1
|
|
135
170
|
export CONFIG_FILE="$root/intelligence.yaml"
|
|
136
171
|
export REPO_ROOT="$root"
|
|
137
|
-
export
|
|
172
|
+
export IS_CONTENT_REL="$content_rel"
|
|
138
173
|
export IS_MODULE_REL="$SYNC_PKG_STORE"
|
|
139
174
|
export IS_SYNC_CMD="intelligence sync"
|
|
140
175
|
export IS_MANIFEST_NAME="intelligence.yaml"
|
|
141
|
-
export IS_PROTECTED_DIRS="$
|
|
176
|
+
export IS_PROTECTED_DIRS="$content_rel:.intelligence"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# --- Project lifecycle preflight -----------------------------------------
|
|
180
|
+
# Public commands are intentionally few. They share this state gate so a CLI
|
|
181
|
+
# installed at a newer engine version brings the current v2 project forward
|
|
182
|
+
# before a mutating operation. The npm install itself cannot do that: it runs
|
|
183
|
+
# outside any project and does not know which repositories the user owns.
|
|
184
|
+
|
|
185
|
+
is_ci_environment() {
|
|
186
|
+
case "${CI:-}" in
|
|
187
|
+
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
188
|
+
*) return 1 ;;
|
|
189
|
+
esac
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# Validate a repository-relative project content directory, including every
|
|
193
|
+
# existing symlinked path component. Call before writing a new manifest.
|
|
194
|
+
assert_safe_content_dir() {
|
|
195
|
+
local root="$1" content_dir="$2" repo_phys probe old_ifs part probe_phys
|
|
196
|
+
local -a parts
|
|
197
|
+
case "$content_dir" in
|
|
198
|
+
""|/*|.|..|../*|*/../*|*/..|.git|.git/*|.intelligence|.intelligence/*|*\\*|[A-Za-z]:*)
|
|
199
|
+
die "unsafe content directory '$content_dir'"
|
|
200
|
+
;;
|
|
201
|
+
esac
|
|
202
|
+
repo_phys="$(cd "$root" && pwd -P)"
|
|
203
|
+
probe="$root"
|
|
204
|
+
old_ifs="$IFS"; IFS='/'; read -r -a parts <<< "$content_dir"; IFS="$old_ifs"
|
|
205
|
+
for part in "${parts[@]}"; do
|
|
206
|
+
[ -n "$part" ] || continue
|
|
207
|
+
probe="$probe/$part"
|
|
208
|
+
if [ -e "$probe" ] || [ -L "$probe" ]; then
|
|
209
|
+
probe_phys="$(cd "$probe" 2>/dev/null && pwd -P)" \
|
|
210
|
+
|| die "cannot resolve project content path '$content_dir'"
|
|
211
|
+
case "$probe_phys" in
|
|
212
|
+
"$repo_phys"|"$repo_phys"/*) ;;
|
|
213
|
+
*) die "project content directory resolves outside the repository: '$content_dir'" ;;
|
|
214
|
+
esac
|
|
215
|
+
fi
|
|
216
|
+
done
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
project_needs_upgrade() {
|
|
220
|
+
local root="$1" manifest="$1/intelligence.yaml" stamp eng pinned locked name
|
|
221
|
+
[ -f "$manifest" ] || return 1
|
|
222
|
+
stamp="$(read_schema_version "$manifest")"
|
|
223
|
+
eng="$(bundled_engine_version)"
|
|
224
|
+
[ -z "$stamp" ] && return 0
|
|
225
|
+
[ -n "$(top_scalar "$manifest" "sync_version")" ] && return 0
|
|
226
|
+
_ver_gt "$eng" "$stamp" && return 0
|
|
227
|
+
[ -d "$root/.intelligence/engine" ] && return 0
|
|
228
|
+
|
|
229
|
+
while IFS= read -r name; do
|
|
230
|
+
[ "$name" = "$SYNC_PKG_NAME" ] || continue
|
|
231
|
+
pinned="$(qmap_field "$manifest" "packages" "$name" "version")"
|
|
232
|
+
locked="$(qmap_field "$root/intelligence.lock" "packages" "$name" "resolved")"
|
|
233
|
+
[ "$pinned" = "$eng" ] || return 0
|
|
234
|
+
if [ -f "$root/intelligence.lock" ]; then
|
|
235
|
+
[ -n "$locked" ] || return 0
|
|
236
|
+
[ "${locked#v}" = "$eng" ] || return 0
|
|
237
|
+
fi
|
|
238
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
239
|
+
return 1
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# A manifest with packages but no lock has no trustworthy resolved state from
|
|
243
|
+
# which lifecycle alignment can proceed. Never manufacture a partial lock.
|
|
244
|
+
project_has_packages() {
|
|
245
|
+
local root="$1" name found=1
|
|
246
|
+
while IFS= read -r name; do
|
|
247
|
+
[ -n "$name" ] || continue
|
|
248
|
+
assert_valid_pkg_name "$name"
|
|
249
|
+
found=0
|
|
250
|
+
done < <(qmap_keys "$root/intelligence.yaml" "packages")
|
|
251
|
+
return "$found"
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ensure_project_current() {
|
|
255
|
+
local root="$1" explicit="${2:-}" manifest="$1/intelligence.yaml" stamp eng
|
|
256
|
+
check_version_compat "$manifest" || return $?
|
|
257
|
+
if project_has_packages "$root" && [ ! -f "$root/intelligence.lock" ]; then
|
|
258
|
+
die "manifest declares packages but intelligence.lock is absent — restore the committed lock before running project lifecycle commands"
|
|
259
|
+
fi
|
|
260
|
+
project_needs_upgrade "$root" || return 0
|
|
261
|
+
stamp="$(read_schema_version "$manifest")"
|
|
262
|
+
eng="$(bundled_engine_version)"
|
|
263
|
+
if is_ci_environment && [ "$explicit" != "--explicit" ]; then
|
|
264
|
+
die "project lifecycle requires alignment (stamp ${stamp:-unstamped}, engine $eng) — run 'intelligence init --apply' locally, review and commit the diff"
|
|
265
|
+
fi
|
|
266
|
+
echo " project alignment: stamp ${stamp:-unstamped}, engine $eng"
|
|
267
|
+
bash "$CLI_DIR/internal/upgrade-v2.sh" --no-sync
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
project_store_missing() {
|
|
271
|
+
local root="$1" manifest="$1/intelligence.yaml" name src
|
|
272
|
+
while IFS= read -r name; do
|
|
273
|
+
[ -n "$name" ] || continue
|
|
274
|
+
assert_valid_pkg_name "$name"
|
|
275
|
+
[ -d "$root/.intelligence/packages/$name" ] || return 0
|
|
276
|
+
done < <(qmap_keys "$manifest" "packages")
|
|
277
|
+
for section in rules agents skills; do
|
|
278
|
+
while IFS= read -r src; do
|
|
279
|
+
case "$src" in
|
|
280
|
+
.intelligence/packages/*)
|
|
281
|
+
[ -d "$root/$src" ] || return 0
|
|
282
|
+
;;
|
|
283
|
+
esac
|
|
284
|
+
done < <(read_yaml_list "$manifest" "$section")
|
|
285
|
+
done
|
|
286
|
+
return 1
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
restore_project_store_if_missing() {
|
|
290
|
+
local root="$1"
|
|
291
|
+
project_store_missing "$root" || return 0
|
|
292
|
+
[ -f "$root/intelligence.lock" ] || die "package store is missing and intelligence.lock is absent — run 'intelligence init'"
|
|
293
|
+
echo " restoring package store from intelligence.lock"
|
|
294
|
+
bash "$CLI_DIR/internal/restore.sh" --frozen --no-sync
|
|
142
295
|
}
|
package/cli/lib/lockfile.sh
CHANGED
|
@@ -12,7 +12,7 @@ LOCK_SEP=$'\x1f'
|
|
|
12
12
|
|
|
13
13
|
# top_scalar <file> <key> — one top-level scalar (`key: "value"`), quotes and
|
|
14
14
|
# trailing comment stripped. The declared exception to "no new parsing": the
|
|
15
|
-
# engine's
|
|
15
|
+
# engine's read_schema_version is hardwired to its own key.
|
|
16
16
|
top_scalar() {
|
|
17
17
|
[ -f "$1" ] || return 0
|
|
18
18
|
awk -v key="$2" '
|
|
@@ -54,14 +54,18 @@ lock_write_from_tsv() {
|
|
|
54
54
|
echo "lockfile_version: $LOCKFILE_VERSION"
|
|
55
55
|
echo "engine_version: \"$(bundled_engine_version)\""
|
|
56
56
|
echo "packages:"
|
|
57
|
+
# Values reach a double-quoted YAML scalar: a `"` or `\` in any of
|
|
58
|
+
# them (registry-supplied url/path included) must be escaped, or it
|
|
59
|
+
# would break or alias the neighbouring field.
|
|
57
60
|
awk -F"$LOCK_SEP" '
|
|
61
|
+
function esc(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s }
|
|
58
62
|
NF >= 6 {
|
|
59
|
-
print " \"" $1 "\":"
|
|
60
|
-
if ($2 != "") print " requested: \"" $2 "\""
|
|
61
|
-
print " url: \"" $3 "\""
|
|
62
|
-
if ($4 != "") print " path: \"" $4 "\""
|
|
63
|
-
if ($5 != "") print " resolved: \"" $5 "\""
|
|
64
|
-
print " sha: \"" $6 "\""
|
|
63
|
+
print " \"" esc($1) "\":"
|
|
64
|
+
if ($2 != "") print " requested: \"" esc($2) "\""
|
|
65
|
+
print " url: \"" esc($3) "\""
|
|
66
|
+
if ($4 != "") print " path: \"" esc($4) "\""
|
|
67
|
+
if ($5 != "") print " resolved: \"" esc($5) "\""
|
|
68
|
+
print " sha: \"" esc($6) "\""
|
|
65
69
|
}
|
|
66
70
|
' "$tsv"
|
|
67
71
|
} > "$tmp"
|
|
@@ -72,7 +76,8 @@ lock_write_from_tsv() {
|
|
|
72
76
|
# Replace-or-append one entry, keeping the others.
|
|
73
77
|
lock_upsert() {
|
|
74
78
|
local lock="$1" name="$2" requested="$3" url="$4" path="$5" resolved="$6" sha="$7"
|
|
75
|
-
local tsv
|
|
79
|
+
local tsv
|
|
80
|
+
tsv="$(mktemp -t intelligence-lock-XXXXXX 2>/dev/null || mktemp)"
|
|
76
81
|
{
|
|
77
82
|
lock_to_tsv "$lock" | awk -F"$LOCK_SEP" -v n="$name" '$1 != n'
|
|
78
83
|
printf '%s\037%s\037%s\037%s\037%s\037%s\n' \
|
|
@@ -86,7 +91,8 @@ lock_upsert() {
|
|
|
86
91
|
lock_remove() {
|
|
87
92
|
local lock="$1" name="$2"
|
|
88
93
|
[ -f "$lock" ] || return 0
|
|
89
|
-
local tsv
|
|
94
|
+
local tsv
|
|
95
|
+
tsv="$(mktemp -t intelligence-lock-XXXXXX 2>/dev/null || mktemp)"
|
|
90
96
|
lock_to_tsv "$lock" | awk -F"$LOCK_SEP" -v n="$name" '$1 != n' > "$tsv"
|
|
91
97
|
if [ -s "$tsv" ]; then
|
|
92
98
|
lock_write_from_tsv "$lock" "$tsv"
|
package/cli/lib/manifest.sh
CHANGED
|
@@ -36,6 +36,92 @@ assert_valid_pkg_name() {
|
|
|
36
36
|
esac
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
# Target names become adapter filenames and shell function suffixes, so keep
|
|
40
|
+
# them inside the portable shell-identifier subset used by built-in adapters.
|
|
41
|
+
assert_valid_target_name() {
|
|
42
|
+
case "$1" in
|
|
43
|
+
""|[!abcdefghijklmnopqrstuvwxyz]*|*[!abcdefghijklmnopqrstuvwxyz0123456789_]*)
|
|
44
|
+
die "invalid target name '$1' — expected [a-z][a-z0-9_]*"
|
|
45
|
+
;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# target_exists <manifest> <name> — distinguish a disabled target from one
|
|
50
|
+
# that is not declared at all. Both inline and block target shapes count.
|
|
51
|
+
target_exists() {
|
|
52
|
+
local file="$1" target="$2"
|
|
53
|
+
[ -f "$file" ] || return 1
|
|
54
|
+
awk -v target="$target" '
|
|
55
|
+
{ sub(/\r$/, "") }
|
|
56
|
+
/^targets:[[:space:]]*$/ { in_targets = 1; next }
|
|
57
|
+
/^[A-Za-z]/ { in_targets = 0 }
|
|
58
|
+
in_targets && $0 ~ "^ " target ":[[:space:]]*" { found = 1; exit }
|
|
59
|
+
END { exit(found ? 0 : 1) }
|
|
60
|
+
' "$file"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# target_set_enabled <manifest> <name> <true|false> <default-output>
|
|
64
|
+
# Transactionally update only `enabled`, preserving every other target field
|
|
65
|
+
# and comment. A missing target is added in the compact form used by init.
|
|
66
|
+
target_set_enabled() {
|
|
67
|
+
local file="$1" target="$2" enabled="$3" output="$4"
|
|
68
|
+
[ -f "$file" ] || die "no such file: $file"
|
|
69
|
+
case "$enabled" in true|false) ;; *) die "internal: invalid target state '$enabled'" ;; esac
|
|
70
|
+
output="$(_yq_esc "$output")"
|
|
71
|
+
_qmap_stage "$file" -v target="$target" -v enabled="$enabled" -v output="$output" '
|
|
72
|
+
function entry() { return " " target ": { enabled: " enabled ", output: \"" output "\" }" }
|
|
73
|
+
function add_enabled_to_inline(line, p) {
|
|
74
|
+
p = index(line, "{")
|
|
75
|
+
if (p > 0) return substr(line, 1, p) " enabled: " enabled "," substr(line, p + 1)
|
|
76
|
+
return line
|
|
77
|
+
}
|
|
78
|
+
function flush_target() {
|
|
79
|
+
if (in_target && !enabled_seen) print " enabled: " enabled
|
|
80
|
+
in_target = 0
|
|
81
|
+
}
|
|
82
|
+
function flush_targets() {
|
|
83
|
+
flush_target()
|
|
84
|
+
if (in_targets && !target_seen) { print entry(); target_seen = 1 }
|
|
85
|
+
in_targets = 0
|
|
86
|
+
}
|
|
87
|
+
{ sub(/\r$/, "") }
|
|
88
|
+
/^targets:[[:space:]]*$/ { targets_seen = 1; in_targets = 1; print; next }
|
|
89
|
+
in_targets && /^[A-Za-z]/ { flush_targets() }
|
|
90
|
+
in_targets && /^ [A-Za-z]/ {
|
|
91
|
+
flush_target()
|
|
92
|
+
if ($0 ~ "^ " target ":[[:space:]]*") {
|
|
93
|
+
target_seen = 1
|
|
94
|
+
if (match($0, /enabled:[[:space:]]*(true|false)/)) {
|
|
95
|
+
print substr($0, 1, RSTART - 1) "enabled: " enabled substr($0, RSTART + RLENGTH)
|
|
96
|
+
enabled_seen = 1
|
|
97
|
+
} else if ($0 ~ /\{/) {
|
|
98
|
+
print add_enabled_to_inline($0)
|
|
99
|
+
enabled_seen = 1
|
|
100
|
+
} else {
|
|
101
|
+
print
|
|
102
|
+
in_target = 1
|
|
103
|
+
enabled_seen = 0
|
|
104
|
+
}
|
|
105
|
+
next
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
in_target && /^ enabled:[[:space:]]*/ {
|
|
109
|
+
print " enabled: " enabled
|
|
110
|
+
enabled_seen = 1
|
|
111
|
+
next
|
|
112
|
+
}
|
|
113
|
+
{ last = $0; print }
|
|
114
|
+
END {
|
|
115
|
+
flush_targets()
|
|
116
|
+
if (!targets_seen) {
|
|
117
|
+
if (last != "") print ""
|
|
118
|
+
print "targets:"
|
|
119
|
+
print entry()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
'
|
|
123
|
+
}
|
|
124
|
+
|
|
39
125
|
# qmap_keys <file> <block> — quoted keys, one per line.
|
|
40
126
|
qmap_keys() {
|
|
41
127
|
[ -f "$1" ] || return 0
|
|
@@ -114,9 +200,19 @@ _qmap_stage() {
|
|
|
114
200
|
|
|
115
201
|
# qmap_set <file> <block> <key> <field> <value> — upsert one field, creating
|
|
116
202
|
# the block and the key as needed.
|
|
203
|
+
# _yq_esc <string> — escape \ and " for a double-quoted YAML scalar.
|
|
204
|
+
_yq_esc() {
|
|
205
|
+
local s="$1"
|
|
206
|
+
s="${s//\\/\\\\}"
|
|
207
|
+
printf '%s' "${s//\"/\\\"}"
|
|
208
|
+
}
|
|
209
|
+
|
|
117
210
|
qmap_set() {
|
|
118
211
|
local file="$1" block="$2" key="$3" field="$4" value="$5"
|
|
119
212
|
[ -f "$file" ] || die "no such file: $file"
|
|
213
|
+
# url/path values can carry a `"`; escape before it reaches the quoted
|
|
214
|
+
# scalar the writer emits.
|
|
215
|
+
value="$(_yq_esc "$value")"
|
|
120
216
|
_qmap_stage "$file" -v block="$block" -v key="$key" -v field="$field" -v value="$value" '
|
|
121
217
|
function keyline() { return " \"" key "\":" }
|
|
122
218
|
function fieldline() { return " " field ": \"" value "\"" }
|
|
@@ -163,6 +259,7 @@ qmap_set() {
|
|
|
163
259
|
qmap_set_value() {
|
|
164
260
|
local file="$1" block="$2" key="$3" value="$4"
|
|
165
261
|
[ -f "$file" ] || die "no such file: $file"
|
|
262
|
+
value="$(_yq_esc "$value")"
|
|
166
263
|
_qmap_stage "$file" -v block="$block" -v key="$key" -v value="$value" '
|
|
167
264
|
function entry() { return " \"" key "\": \"" value "\"" }
|
|
168
265
|
{ sub(/\r$/, "") }
|
|
@@ -282,6 +379,14 @@ registries_remove() {
|
|
|
282
379
|
{ print }
|
|
283
380
|
'
|
|
284
381
|
}
|
|
382
|
+
# True (0) if <config> already lists <entry> under any sources section. Quoted
|
|
383
|
+
# and bare spellings both count - a manifest may hold either.
|
|
384
|
+
_mig_has_source() {
|
|
385
|
+
local config="$1" entry="$2"
|
|
386
|
+
[ -f "$config" ] || return 1
|
|
387
|
+
grep -Fq -- "\"$entry\"" "$config" || grep -Fq -- "- $entry" "$config"
|
|
388
|
+
}
|
|
389
|
+
|
|
285
390
|
|
|
286
391
|
# sources_add_entry_first <file> <section> <entry> — idempotent insert at the
|
|
287
392
|
# TOP of sources.<section> (creating sources:/section as needed). Package
|
|
@@ -313,7 +418,7 @@ sources_add_entry_first() {
|
|
|
313
418
|
}
|
|
314
419
|
|
|
315
420
|
# sources_remove_entry <file> <section> <entry> — remove `- "entry"` from
|
|
316
|
-
# sources.<section>.
|
|
421
|
+
# sources.<section>.
|
|
317
422
|
sources_remove_entry() {
|
|
318
423
|
local file="$1" section="$2" entry="$3"
|
|
319
424
|
[ -f "$file" ] || return 0
|
package/cli/lib/registry.sh
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# visible, reviewable, deletable.
|
|
12
12
|
#
|
|
13
13
|
# The NAME is the trust anchor a developer reasons with; source integrity is
|
|
14
|
-
# a separate mechanism: the lock pins url+sha, and
|
|
14
|
+
# a separate mechanism: the lock pins url+sha, and `status --check` flags
|
|
15
15
|
# resolution/lock url drift.
|
|
16
16
|
#
|
|
17
17
|
# An index is itself fetched with git (never curl): auth, proxies and private
|
|
@@ -21,16 +21,26 @@
|
|
|
21
21
|
# _fetch_index <registry-repo-url> — clones the registry repo (shallow) and
|
|
22
22
|
# prints the path of its index.yaml; empty on failure. Cached per run+url.
|
|
23
23
|
_fetch_index() {
|
|
24
|
-
local url="$1"
|
|
25
|
-
|
|
24
|
+
local url="$1" key tmp
|
|
25
|
+
assert_safe_source_url "$url"
|
|
26
|
+
# Per-run private cache dir under an UNPREDICTABLE mktemp path (a fixed
|
|
27
|
+
# /tmp/...-$$ would be trusted on cache hit and pre-creatable by others).
|
|
28
|
+
# No EXIT trap: _fetch_index is always called inside $( ), so a trap would
|
|
29
|
+
# fire in that subshell and delete the dir before the caller reads it. The
|
|
30
|
+
# dir is small and OS-reaped from the temp root; IS_REMOTE_CACHE-style
|
|
31
|
+
# persistence is exported so repeated calls in one command share it.
|
|
32
|
+
if [ -z "${IS_CLI_INDEX_CACHE:-}" ]; then
|
|
33
|
+
IS_CLI_INDEX_CACHE="$(mktemp -d -t intelligence-cli-index-XXXXXX 2>/dev/null || mktemp -d)"
|
|
34
|
+
export IS_CLI_INDEX_CACHE
|
|
35
|
+
fi
|
|
36
|
+
local cache="$IS_CLI_INDEX_CACHE"
|
|
26
37
|
key="$(printf '%s' "$url" | cksum | awk '{print $1}')"
|
|
27
38
|
if [ -f "$cache/$key/index.yaml" ]; then
|
|
28
39
|
printf '%s' "$cache/$key/index.yaml"
|
|
29
40
|
return 0
|
|
30
41
|
fi
|
|
31
|
-
mkdir -p "$cache"
|
|
32
42
|
tmp="$cache/$key"
|
|
33
|
-
if GIT_TERMINAL_PROMPT=0 git -c core.autocrlf=false clone --depth 1 --quiet "$url" "$tmp" 2>/dev/null \
|
|
43
|
+
if GIT_TERMINAL_PROMPT=0 git -c core.autocrlf=false clone --depth 1 --quiet -- "$url" "$tmp" 2>/dev/null \
|
|
34
44
|
&& [ -f "$tmp/index.yaml" ]; then
|
|
35
45
|
printf '%s' "$tmp/index.yaml"
|
|
36
46
|
fi
|
|
@@ -83,7 +93,7 @@ suggest_similar() {
|
|
|
83
93
|
cshort="${cand#*/}"; cshort="${cshort%s}"
|
|
84
94
|
if [ "$cshort" = "$want" ] && [ "$cand" != "$name" ]; then
|
|
85
95
|
seen="$seen$cand "
|
|
86
|
-
echo " Did you mean: intelligence add $cand" >&2
|
|
96
|
+
echo " Did you mean: intelligence package add $cand" >&2
|
|
87
97
|
fi
|
|
88
98
|
done < <(qmap_keys "$idx" "packages")
|
|
89
99
|
}
|
|
@@ -103,19 +113,23 @@ suggest_similar() {
|
|
|
103
113
|
# store holds the bytes the package published.
|
|
104
114
|
fetch_package() {
|
|
105
115
|
local url="$1" ref="$2" subpath="$3" dest="$4"
|
|
116
|
+
assert_safe_source_url "$url"
|
|
117
|
+
assert_safe_ref "$ref"
|
|
106
118
|
# Bundle seed: the engine-content package at the CLI's own version copies
|
|
107
|
-
# from the npm bundle — no network, which keeps init
|
|
108
|
-
#
|
|
119
|
+
# from the npm bundle — no network, which keeps init, sync restoration and
|
|
120
|
+
# archived-project conversion offline. Keyed on the
|
|
109
121
|
# full (url, path, ref) triple; any other version or source falls through
|
|
110
122
|
# to the normal clone.
|
|
111
123
|
if [ "$url" = "$SYNC_PKG_URL" ] && [ "$subpath" = "$SYNC_PKG_PATH" ] \
|
|
112
124
|
&& [ "${ref#v}" = "$(bundled_engine_version)" ]; then
|
|
125
|
+
[ -n "${IS_BUNDLED_PKG_DIR:-}" ] && [ -d "$IS_BUNDLED_PKG_DIR" ] \
|
|
126
|
+
|| die "bundled engine content not found next to the CLI — reinstall @ainova-systems/intelligence"
|
|
113
127
|
rm -rf "$dest"
|
|
114
128
|
mkdir -p "$dest"
|
|
115
|
-
cp -R "$
|
|
129
|
+
cp -R "$IS_BUNDLED_PKG_DIR/." "$dest/"
|
|
116
130
|
rm -rf "$dest/.git"
|
|
117
|
-
if [ -f "$IS_ENGINE_DIR/
|
|
118
|
-
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/
|
|
131
|
+
if [ -f "$IS_ENGINE_DIR/ENGINE_SHA" ]; then
|
|
132
|
+
tr -d ' \t\r\n' < "$IS_ENGINE_DIR/ENGINE_SHA"
|
|
119
133
|
fi
|
|
120
134
|
return 0
|
|
121
135
|
fi
|
|
@@ -124,20 +138,20 @@ fetch_package() {
|
|
|
124
138
|
[ -n "$ref" ] && branch_arg=(--branch "$ref")
|
|
125
139
|
tmp="$(mktemp -d -t intelligence-pkg-XXXXXX 2>/dev/null || mktemp -d)"
|
|
126
140
|
if ! GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
|
|
127
|
-
clone --depth 1 "${branch_arg[@]+"${branch_arg[@]}"}" --quiet "$url" "$tmp/clone" 2>/dev/null; then
|
|
141
|
+
clone --depth 1 "${branch_arg[@]+"${branch_arg[@]}"}" --quiet -- "$url" "$tmp/clone" 2>/dev/null; then
|
|
128
142
|
# A SHA is not clonable with --branch — fall back to a full clone + checkout.
|
|
129
143
|
rm -rf "$tmp/clone"
|
|
130
144
|
GIT_TERMINAL_PROMPT=0 git -c core.symlinks=false -c core.autocrlf=false -c core.eol=lf \
|
|
131
|
-
clone --quiet "$url" "$tmp/clone" 2>/dev/null \
|
|
145
|
+
clone --quiet -- "$url" "$tmp/clone" 2>/dev/null \
|
|
132
146
|
|| { rm -rf "$tmp"; die "cannot clone $url"; }
|
|
133
|
-
[ -z "$ref" ] || git -C "$tmp/clone" checkout --quiet "$ref" \
|
|
147
|
+
[ -z "$ref" ] || git -C "$tmp/clone" checkout --quiet "$ref" -- \
|
|
134
148
|
|| { rm -rf "$tmp"; die "ref '$ref' not found in $url"; }
|
|
135
149
|
fi
|
|
136
150
|
sha="$(git -C "$tmp/clone" rev-parse HEAD)"
|
|
137
151
|
src="$tmp/clone"
|
|
138
152
|
if [ -n "$subpath" ]; then
|
|
139
153
|
case "$subpath" in
|
|
140
|
-
|
|
154
|
+
*..*|/*|*[\"\']*) rm -rf "$tmp"; die "unsafe path '$subpath'" ;;
|
|
141
155
|
esac
|
|
142
156
|
src="$tmp/clone/$subpath"
|
|
143
157
|
[ -d "$src" ] || { rm -rf "$tmp"; die "path '$subpath' not found in $url${ref:+@$ref}" ; }
|