@ainova-systems/intelligence 0.16.0 → 0.16.1
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/lib/gitignore.sh +161 -18
- package/engine/ENGINE_SHA +1 -1
- package/engine/VERSION +1 -1
- package/package.json +1 -1
package/cli/lib/gitignore.sh
CHANGED
|
@@ -1,12 +1,56 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Generated-output policy derived from each adapter's ownership contract.
|
|
3
3
|
|
|
4
|
+
# The line that separates what this policy manages from what the project wrote
|
|
5
|
+
# before Intelligence touched the file: everything the CLI appends lands after
|
|
6
|
+
# it, so it is also the boundary of what the CLI may rewrite.
|
|
7
|
+
IS_GITIGNORE_HEADER='# Intelligence generated state and tool output'
|
|
8
|
+
|
|
9
|
+
# Line endings held in variables, never written as `$'\r'` at the point of use:
|
|
10
|
+
# inside a command substitution bash does not expand that form, so the very same
|
|
11
|
+
# expression stops stripping the CR depending on where it is called from.
|
|
12
|
+
IS_CR=$'\r'
|
|
13
|
+
IS_LF=$'\n'
|
|
14
|
+
IS_CRLF=$'\r\n'
|
|
15
|
+
|
|
16
|
+
# An ignore file checked out on Windows can hold CRLF, and a CR belongs to the
|
|
17
|
+
# pattern Git matches — so these two helpers decide presence ignoring it, and
|
|
18
|
+
# append in whatever ending the file already uses. Comparing with the CR
|
|
19
|
+
# attached made every existing line look absent on Linux and macOS (where it is
|
|
20
|
+
# part of the line, unlike under MSYS), so each alignment appended an LF copy of
|
|
21
|
+
# a line that was already there and the file grew forever.
|
|
22
|
+
|
|
23
|
+
# ignore_file_eol_var <file> — set IS_IGNORE_EOL from the file's first line.
|
|
24
|
+
ignore_file_eol_var() {
|
|
25
|
+
local file="$1" first=""
|
|
26
|
+
IS_IGNORE_EOL="$IS_LF"
|
|
27
|
+
[ -s "$file" ] || return 0
|
|
28
|
+
IFS= read -r first < "$file" || true
|
|
29
|
+
case "$first" in
|
|
30
|
+
*"$IS_CR") IS_IGNORE_EOL="$IS_CRLF" ;;
|
|
31
|
+
esac
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# ignore_file_has_line <file> <line> — true when the file lists exactly this
|
|
35
|
+
# line, with or without a trailing CR.
|
|
36
|
+
ignore_file_has_line() {
|
|
37
|
+
local file="$1" line="$2"
|
|
38
|
+
[ -f "$file" ] || return 1
|
|
39
|
+
grep -Fqx -- "$line" "$file" 2>/dev/null && return 0
|
|
40
|
+
grep -Fqx -- "$line$IS_CR" "$file" 2>/dev/null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ignore_file_append_line <file> <line> — append in the file's own ending.
|
|
44
|
+
ignore_file_append_line() {
|
|
45
|
+
local file="$1" line="$2"
|
|
46
|
+
ignore_file_eol_var "$file"
|
|
47
|
+
printf '%s%s' "$line" "$IS_IGNORE_EOL" >> "$file"
|
|
48
|
+
}
|
|
49
|
+
|
|
4
50
|
gitignore_add_line() {
|
|
5
51
|
local root="$1" line="$2" file="$1/.gitignore"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
fi
|
|
9
|
-
printf '%s\n' "$line" >> "$file"
|
|
52
|
+
ignore_file_has_line "$file" "$line" && return 0
|
|
53
|
+
ignore_file_append_line "$file" "$line"
|
|
10
54
|
}
|
|
11
55
|
|
|
12
56
|
gitignore_path_is_ignored() {
|
|
@@ -21,10 +65,102 @@ gitignore_tail_is() {
|
|
|
21
65
|
[ -f "$file" ] || return 1
|
|
22
66
|
local want tail_lines
|
|
23
67
|
want="$(printf '%s\n' "$@")"
|
|
24
|
-
|
|
68
|
+
# CR stripped for the comparison only — the chain is the same chain whether
|
|
69
|
+
# the file is LF or CRLF.
|
|
70
|
+
tail_lines="$(tail -n "$#" -- "$file" | tr -d '\r')"
|
|
25
71
|
[ "$tail_lines" = "$want" ]
|
|
26
72
|
}
|
|
27
73
|
|
|
74
|
+
# gitignore_collapse_duplicates <root> <probe-path> <line…> — keep only the LAST
|
|
75
|
+
# occurrence of each given line inside the region this policy owns (its header
|
|
76
|
+
# to end of file).
|
|
77
|
+
#
|
|
78
|
+
# This removes residue, never meaning: Git applies the last matching rule, so
|
|
79
|
+
# identical earlier copies of a line decide nothing. The copies exist because
|
|
80
|
+
# the 0.12.0 repair appended the whole negation chain on every alignment — a
|
|
81
|
+
# project carries one chain per run it made, and 0.12.1 only stopped the growth.
|
|
82
|
+
#
|
|
83
|
+
# Lines above the header predate Intelligence and are left untouched, including
|
|
84
|
+
# a hand-written copy of a line this policy also writes. The probe path is
|
|
85
|
+
# re-checked afterwards: a collapse that somehow changed what Git ignores is
|
|
86
|
+
# rolled back rather than committed.
|
|
87
|
+
gitignore_collapse_duplicates() {
|
|
88
|
+
local root="$1" probe="$2"; shift 2
|
|
89
|
+
local file="$root/.gitignore" tmp backup before=0 after=0
|
|
90
|
+
local -a lines=() managed=("$@") last_idx=()
|
|
91
|
+
local line key i mi n hdr=-1 keep final_newline=1
|
|
92
|
+
[ -f "$file" ] || return 0
|
|
93
|
+
|
|
94
|
+
# Read and write the file's own bytes, without awk: on Windows it reads in
|
|
95
|
+
# text mode and would hand back every line stripped of its CR, rewriting a
|
|
96
|
+
# CRLF file as LF. A line's CR is significant to Git, and either way the
|
|
97
|
+
# lines this function does not remove must survive byte-for-byte.
|
|
98
|
+
line=""
|
|
99
|
+
while IFS= read -r line; do
|
|
100
|
+
lines[${#lines[@]}]="$line"
|
|
101
|
+
line=""
|
|
102
|
+
done < "$file"
|
|
103
|
+
# `read` returns 1 at EOF but still fills `line` when the file's last line
|
|
104
|
+
# carries no newline; preserve that ending instead of adding one.
|
|
105
|
+
if [ -n "$line" ]; then
|
|
106
|
+
lines[${#lines[@]}]="$line"
|
|
107
|
+
final_newline=0
|
|
108
|
+
fi
|
|
109
|
+
n=${#lines[@]}
|
|
110
|
+
for ((i = 0; i < n; i++)); do
|
|
111
|
+
if [ "${lines[i]%"$IS_CR"}" = "$IS_GITIGNORE_HEADER" ]; then hdr=$i; break; fi
|
|
112
|
+
done
|
|
113
|
+
[ "$hdr" -ge 0 ] || return 0
|
|
114
|
+
|
|
115
|
+
# Where each managed line last occurs below the header. Indexed arrays and
|
|
116
|
+
# arithmetic `for` only — Bash 3.2 has no associative arrays.
|
|
117
|
+
for ((mi = 0; mi < ${#managed[@]}; mi++)); do
|
|
118
|
+
last_idx[mi]=-1
|
|
119
|
+
for ((i = hdr + 1; i < n; i++)); do
|
|
120
|
+
[ "${lines[i]%"$IS_CR"}" = "${managed[mi]}" ] && last_idx[mi]=$i
|
|
121
|
+
done
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
tmp="$file.cli.tmp"
|
|
125
|
+
{
|
|
126
|
+
for ((i = 0; i < n; i++)); do
|
|
127
|
+
keep=1
|
|
128
|
+
if [ "$i" -gt "$hdr" ]; then
|
|
129
|
+
key="${lines[i]%"$IS_CR"}"
|
|
130
|
+
for ((mi = 0; mi < ${#managed[@]}; mi++)); do
|
|
131
|
+
if [ "$key" = "${managed[mi]}" ] && [ "$i" -ne "${last_idx[mi]}" ]; then
|
|
132
|
+
keep=0
|
|
133
|
+
break
|
|
134
|
+
fi
|
|
135
|
+
done
|
|
136
|
+
fi
|
|
137
|
+
[ "$keep" -eq 1 ] || continue
|
|
138
|
+
if [ "$i" -eq $((n - 1)) ] && [ "$final_newline" -eq 0 ]; then
|
|
139
|
+
printf '%s' "${lines[i]}"
|
|
140
|
+
else
|
|
141
|
+
printf '%s\n' "${lines[i]}"
|
|
142
|
+
fi
|
|
143
|
+
done
|
|
144
|
+
} > "$tmp"
|
|
145
|
+
if [ ! -s "$tmp" ] || cmp -s "$tmp" "$file"; then
|
|
146
|
+
rm -f "$tmp"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
before=0
|
|
151
|
+
gitignore_path_is_ignored "$root" "$probe" || before=$?
|
|
152
|
+
backup="$file.cli.bak"
|
|
153
|
+
cp -- "$file" "$backup"
|
|
154
|
+
mv -- "$tmp" "$file"
|
|
155
|
+
after=0
|
|
156
|
+
gitignore_path_is_ignored "$root" "$probe" || after=$?
|
|
157
|
+
if [ "$after" != "$before" ]; then
|
|
158
|
+
mv -- "$backup" "$file"
|
|
159
|
+
return 0
|
|
160
|
+
fi
|
|
161
|
+
rm -f "$backup"
|
|
162
|
+
}
|
|
163
|
+
|
|
28
164
|
# Git cannot re-include a child of an excluded directory, so an include needs an
|
|
29
165
|
# explicit negation for every parent. A negation only wins when it comes after
|
|
30
166
|
# the rule excluding the parent, and the only repair an append can make is to
|
|
@@ -59,7 +195,7 @@ gitignore_add_effective_include() {
|
|
|
59
195
|
gitignore_path_is_ignored "$root" "$value" || rc=$?
|
|
60
196
|
case "$rc" in
|
|
61
197
|
0) ;;
|
|
62
|
-
1|2) return 0 ;;
|
|
198
|
+
1|2) gitignore_collapse_duplicates "$root" "$value" "${negations[@]}"; return 0 ;;
|
|
63
199
|
*) return "$rc" ;;
|
|
64
200
|
esac
|
|
65
201
|
|
|
@@ -67,17 +203,25 @@ gitignore_add_effective_include() {
|
|
|
67
203
|
# chain last. Already last means appending cannot help — leave it, and let
|
|
68
204
|
# `status --check` report the re-inclusion this policy could not make
|
|
69
205
|
# effective rather than growing the file forever.
|
|
70
|
-
gitignore_tail_is "$root/.gitignore" "${negations[@]}"
|
|
71
|
-
|
|
206
|
+
if ! gitignore_tail_is "$root/.gitignore" "${negations[@]}"; then
|
|
207
|
+
for line in "${negations[@]}"; do
|
|
208
|
+
ignore_file_append_line "$root/.gitignore" "$line"
|
|
209
|
+
done
|
|
210
|
+
fi
|
|
211
|
+
# Either way the chain now stands last, so any earlier copy of it is the
|
|
212
|
+
# residue an older CLI appended; collapsing runs on both paths so a project
|
|
213
|
+
# converges on one copy whether or not this run had to move anything.
|
|
214
|
+
gitignore_collapse_duplicates "$root" "$value" "${negations[@]}"
|
|
72
215
|
}
|
|
73
216
|
|
|
74
217
|
ensure_gitignore_header() {
|
|
75
218
|
local root="$1" file="$1/.gitignore"
|
|
76
|
-
if
|
|
219
|
+
if ! ignore_file_has_line "$file" "$IS_GITIGNORE_HEADER"; then
|
|
77
220
|
if [ -f "$file" ] && [ -s "$file" ] && [ -n "$(tail -c 1 "$file" 2>/dev/null)" ]; then
|
|
78
|
-
|
|
221
|
+
ignore_file_eol_var "$file"
|
|
222
|
+
printf '%s' "$IS_IGNORE_EOL" >> "$file"
|
|
79
223
|
fi
|
|
80
|
-
|
|
224
|
+
ignore_file_append_line "$file" "$IS_GITIGNORE_HEADER"
|
|
81
225
|
fi
|
|
82
226
|
}
|
|
83
227
|
|
|
@@ -125,10 +269,8 @@ publisher_ignore_file_names() {
|
|
|
125
269
|
|
|
126
270
|
publisher_ignore_add_line() {
|
|
127
271
|
local file="$1" line="$2"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
fi
|
|
131
|
-
printf '%s\n' "$line" >> "$file"
|
|
272
|
+
ignore_file_has_line "$file" "$line" && return 0
|
|
273
|
+
ignore_file_append_line "$file" "$line"
|
|
132
274
|
PUBLISH_IGNORE_FILE_CHANGED=1
|
|
133
275
|
}
|
|
134
276
|
|
|
@@ -148,11 +290,12 @@ ensure_manifest_publisher_ignores() {
|
|
|
148
290
|
file="$root/$rel"
|
|
149
291
|
[ -f "$file" ] || continue
|
|
150
292
|
PUBLISH_IGNORE_FILE_CHANGED=0
|
|
151
|
-
if !
|
|
293
|
+
if ! ignore_file_has_line "$file" '# Intelligence development context and generated output'; then
|
|
152
294
|
if [ -s "$file" ] && [ -n "$(tail -c 1 "$file" 2>/dev/null)" ]; then
|
|
153
|
-
|
|
295
|
+
ignore_file_eol_var "$file"
|
|
296
|
+
printf '%s' "$IS_IGNORE_EOL" >> "$file"
|
|
154
297
|
fi
|
|
155
|
-
|
|
298
|
+
ignore_file_append_line "$file" '# Intelligence development context and generated output'
|
|
156
299
|
PUBLISH_IGNORE_FILE_CHANGED=1
|
|
157
300
|
fi
|
|
158
301
|
publisher_ignore_add_path "$file" '.intelligence'
|
package/engine/ENGINE_SHA
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
365ec21e2f2d4d9ef323ed9f1387b3c3c4bef845
|
package/engine/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.16.
|
|
1
|
+
0.16.1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainova-systems/intelligence",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
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"
|