@jetrabbits/agentic 0.0.5 → 0.2.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/AGENTS.md +7 -0
- package/CHANGELOG.md +12 -0
- package/Makefile +31 -3
- package/README.md +23 -2
- package/agentic +1019 -90
- package/docs/agentic-lifecycle.md +11 -0
- package/docs/agentic-token-minimization/README.md +2 -0
- package/docs/agentic-usage.md +59 -2
- package/docs/opencode_setup.md +3 -0
- package/package.json +2 -1
package/agentic
CHANGED
|
@@ -15,7 +15,7 @@ APP_REPO_LINK="https://github.com/sawrus/agent-guides"
|
|
|
15
15
|
PROJECT_MANIFEST_NAME=".agentic.json"
|
|
16
16
|
|
|
17
17
|
DEFAULT_AGENT_OS="default"
|
|
18
|
-
STATIC_AGENT_OS=(
|
|
18
|
+
STATIC_AGENT_OS=(opencode codex claude antigravity cursor kilocode)
|
|
19
19
|
INSTALL_DIRS=(rules skills workflows prompts)
|
|
20
20
|
THEME_CHOICES=(auto dark light)
|
|
21
21
|
|
|
@@ -55,6 +55,12 @@ SKIPPED_MANAGED_PATHS=()
|
|
|
55
55
|
WARNINGS=()
|
|
56
56
|
|
|
57
57
|
CONTEXT7_API_KEY="${CONTEXT7_API_KEY:-}"
|
|
58
|
+
AGENTIC_ENABLE_CONTEXT7="${AGENTIC_ENABLE_CONTEXT7:-}"
|
|
59
|
+
AGENTIC_DOCTOR="${AGENTIC_DOCTOR:-1}"
|
|
60
|
+
AGENTIC_DOCTOR_KEEP_TMP="${AGENTIC_DOCTOR_KEEP_TMP:-0}"
|
|
61
|
+
|
|
62
|
+
RUN_LOG_ACTIVE=false
|
|
63
|
+
RUN_LOG_FILE=""
|
|
58
64
|
|
|
59
65
|
COLOR_RESET=""
|
|
60
66
|
COLOR_HEADER=""
|
|
@@ -67,7 +73,7 @@ FZF_COLOR_ARGS=()
|
|
|
67
73
|
|
|
68
74
|
usage() {
|
|
69
75
|
cat <<USAGE
|
|
70
|
-
$APP_TITLE
|
|
76
|
+
$APP_TITLE $(app_version_label)
|
|
71
77
|
|
|
72
78
|
Usage:
|
|
73
79
|
$SCRIPT_NAME list [agentos|areas|specs --area <name>]
|
|
@@ -75,6 +81,7 @@ Usage:
|
|
|
75
81
|
$SCRIPT_NAME tui [--theme auto|dark|light]
|
|
76
82
|
$SCRIPT_NAME upgrade
|
|
77
83
|
$SCRIPT_NAME self-install [--bin-dir <dir>] [--force] [--install-fzf] [--dry-run]
|
|
84
|
+
$SCRIPT_NAME --version
|
|
78
85
|
|
|
79
86
|
Behavior:
|
|
80
87
|
- No arguments in interactive terminal: runs TUI mode
|
|
@@ -87,11 +94,13 @@ Options:
|
|
|
87
94
|
--areas Comma-separated area list (example: software)
|
|
88
95
|
--specializations Comma-separated specializations in area.spec format (example: software.backend,software.frontend)
|
|
89
96
|
--theme Interface theme: auto|dark|light (default: config value or auto)
|
|
97
|
+
--no-doctor Skip real agent smoke checks after install
|
|
90
98
|
--bin-dir Installation directory for self-install (default: ~/.local/bin)
|
|
91
99
|
--force Overwrite existing binary for self-install
|
|
92
100
|
--install-fzf During self-install, try to auto-install fzf (optional)
|
|
93
101
|
--dry-run Show actions without writing files
|
|
94
102
|
-h, --help Show this help
|
|
103
|
+
-V, --version Show agentic version
|
|
95
104
|
|
|
96
105
|
Examples:
|
|
97
106
|
$SCRIPT_NAME list agentos
|
|
@@ -102,6 +111,31 @@ Examples:
|
|
|
102
111
|
USAGE
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
read_package_version() {
|
|
115
|
+
local package_file="$1"
|
|
116
|
+
[[ -f "$package_file" ]] || return 1
|
|
117
|
+
|
|
118
|
+
sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$package_file" | head -n 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
app_version() {
|
|
122
|
+
local version=""
|
|
123
|
+
local candidate
|
|
124
|
+
for candidate in "$SCRIPT_DIR/package.json" "$REPO_ROOT/package.json" "$APP_REPO_DIR/package.json"; do
|
|
125
|
+
[[ -n "$candidate" ]] || continue
|
|
126
|
+
version="$(read_package_version "$candidate" || true)"
|
|
127
|
+
if [[ -n "$version" ]]; then
|
|
128
|
+
printf '%s\n' "$version"
|
|
129
|
+
return
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
printf 'unknown\n'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
app_version_label() {
|
|
136
|
+
printf 'v%s\n' "$(app_version)"
|
|
137
|
+
}
|
|
138
|
+
|
|
105
139
|
is_interactive_terminal() {
|
|
106
140
|
if [[ "${AGENTIC_FORCE_INTERACTIVE:-${AGENTOS_FORCE_INTERACTIVE:-}}" == "1" ]]; then
|
|
107
141
|
return 0
|
|
@@ -217,16 +251,116 @@ set_theme_colors() {
|
|
|
217
251
|
}
|
|
218
252
|
|
|
219
253
|
log() {
|
|
220
|
-
|
|
254
|
+
emit_log_line stdout "[agentic]" "$1" "$COLOR_INFO"
|
|
221
255
|
}
|
|
222
256
|
|
|
223
257
|
warn() {
|
|
224
|
-
|
|
258
|
+
emit_log_line stdout "[agentic][warn]" "$1" "$COLOR_WARN"
|
|
225
259
|
WARNINGS+=("$1")
|
|
226
260
|
}
|
|
227
261
|
|
|
228
262
|
error() {
|
|
229
|
-
|
|
263
|
+
emit_log_line stderr "[agentic][error]" "$1" "$COLOR_ERROR"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
timestamp_now() {
|
|
267
|
+
date '+%Y-%m-%d %H:%M:%S'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
init_run_logging() {
|
|
271
|
+
if [[ "$RUN_LOG_ACTIVE" == true ]]; then
|
|
272
|
+
return
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
local base_dir="${TMPDIR:-/tmp}"
|
|
276
|
+
local stamp
|
|
277
|
+
stamp="$(date '+%Y%m%d-%H%M%S')"
|
|
278
|
+
RUN_LOG_FILE="$(mktemp "$base_dir/agentic-$stamp.XXXXXX")"
|
|
279
|
+
RUN_LOG_ACTIVE=true
|
|
280
|
+
log "Run log initialized: $RUN_LOG_FILE"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
write_run_log_line() {
|
|
284
|
+
local line="$1"
|
|
285
|
+
if [[ "$RUN_LOG_ACTIVE" == true && -n "$RUN_LOG_FILE" ]]; then
|
|
286
|
+
printf '%s\n' "$line" >> "$RUN_LOG_FILE"
|
|
287
|
+
fi
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
emit_log_line() {
|
|
291
|
+
local stream="$1"
|
|
292
|
+
local tag="$2"
|
|
293
|
+
local message="$3"
|
|
294
|
+
local color="${4:-}"
|
|
295
|
+
|
|
296
|
+
local line plain_line ts
|
|
297
|
+
if [[ "$RUN_LOG_ACTIVE" == true ]]; then
|
|
298
|
+
ts="$(timestamp_now)"
|
|
299
|
+
plain_line="$ts $tag $message"
|
|
300
|
+
if [[ -n "$color" ]]; then
|
|
301
|
+
line="$ts ${color}${tag}${COLOR_RESET} $message"
|
|
302
|
+
else
|
|
303
|
+
line="$plain_line"
|
|
304
|
+
fi
|
|
305
|
+
else
|
|
306
|
+
plain_line="$tag $message"
|
|
307
|
+
if [[ -n "$color" ]]; then
|
|
308
|
+
line="${color}${tag}${COLOR_RESET} $message"
|
|
309
|
+
else
|
|
310
|
+
line="$plain_line"
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
if [[ "$stream" == "stderr" ]]; then
|
|
315
|
+
printf '%s\n' "$line" >&2
|
|
316
|
+
else
|
|
317
|
+
printf '%s\n' "$line"
|
|
318
|
+
fi
|
|
319
|
+
write_run_log_line "$plain_line"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
out() {
|
|
323
|
+
local message="${1:-}"
|
|
324
|
+
local color="${2:-}"
|
|
325
|
+
local line plain_line ts
|
|
326
|
+
|
|
327
|
+
if [[ -z "$message" ]]; then
|
|
328
|
+
printf '\n'
|
|
329
|
+
write_run_log_line ""
|
|
330
|
+
return
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
if [[ "$RUN_LOG_ACTIVE" == true ]]; then
|
|
334
|
+
ts="$(timestamp_now)"
|
|
335
|
+
plain_line="$ts $message"
|
|
336
|
+
if [[ -n "$color" ]]; then
|
|
337
|
+
line="$ts ${color}${message}${COLOR_RESET}"
|
|
338
|
+
else
|
|
339
|
+
line="$plain_line"
|
|
340
|
+
fi
|
|
341
|
+
else
|
|
342
|
+
plain_line="$message"
|
|
343
|
+
if [[ -n "$color" ]]; then
|
|
344
|
+
line="${color}${message}${COLOR_RESET}"
|
|
345
|
+
else
|
|
346
|
+
line="$plain_line"
|
|
347
|
+
fi
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
printf '%s\n' "$line"
|
|
351
|
+
write_run_log_line "$plain_line"
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
log_file_block() {
|
|
355
|
+
local label="$1"
|
|
356
|
+
local path="$2"
|
|
357
|
+
[[ "$RUN_LOG_ACTIVE" == true && -n "$RUN_LOG_FILE" && -f "$path" ]] || return 0
|
|
358
|
+
|
|
359
|
+
write_run_log_line "$(timestamp_now) --- $label output begin ---"
|
|
360
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
361
|
+
write_run_log_line "$(timestamp_now) $line"
|
|
362
|
+
done < "$path"
|
|
363
|
+
write_run_log_line "$(timestamp_now) --- $label output end ---"
|
|
230
364
|
}
|
|
231
365
|
|
|
232
366
|
unique_append() {
|
|
@@ -519,6 +653,42 @@ ensure_python_available() {
|
|
|
519
653
|
fi
|
|
520
654
|
}
|
|
521
655
|
|
|
656
|
+
pip_command() {
|
|
657
|
+
if command -v pip >/dev/null 2>&1; then
|
|
658
|
+
printf '%s\n' "pip"
|
|
659
|
+
return 0
|
|
660
|
+
fi
|
|
661
|
+
if command -v pip3 >/dev/null 2>&1; then
|
|
662
|
+
printf '%s\n' "pip3"
|
|
663
|
+
return 0
|
|
664
|
+
fi
|
|
665
|
+
if command -v python3 >/dev/null 2>&1 && python3 -m pip --version >/dev/null 2>&1; then
|
|
666
|
+
printf '%s\n' "python3 -m pip"
|
|
667
|
+
return 0
|
|
668
|
+
fi
|
|
669
|
+
return 1
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
ensure_pip_available() {
|
|
673
|
+
if ! pip_command >/dev/null; then
|
|
674
|
+
error "pip is required to run agentic install/tui. Install pip for Python 3 and make 'pip3', 'pip', or 'python3 -m pip' available."
|
|
675
|
+
exit 1
|
|
676
|
+
fi
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
ensure_hash_available() {
|
|
680
|
+
if ! command -v shasum >/dev/null 2>&1 && ! command -v sha256sum >/dev/null 2>&1; then
|
|
681
|
+
error "shasum or sha256sum is required to track managed files"
|
|
682
|
+
exit 1
|
|
683
|
+
fi
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
ensure_agentic_runtime_requirements() {
|
|
687
|
+
ensure_python_available
|
|
688
|
+
ensure_pip_available
|
|
689
|
+
ensure_hash_available
|
|
690
|
+
}
|
|
691
|
+
|
|
522
692
|
selected_agent_os_contains() {
|
|
523
693
|
local expected="$1"
|
|
524
694
|
local agent
|
|
@@ -547,8 +717,7 @@ hash_file() {
|
|
|
547
717
|
elif command -v sha256sum >/dev/null 2>&1; then
|
|
548
718
|
sha256sum "$path" | awk '{print $1}'
|
|
549
719
|
else
|
|
550
|
-
|
|
551
|
-
exit 1
|
|
720
|
+
ensure_hash_available
|
|
552
721
|
fi
|
|
553
722
|
}
|
|
554
723
|
|
|
@@ -634,12 +803,15 @@ register_managed_file() {
|
|
|
634
803
|
local dest="$1"
|
|
635
804
|
local source_ref="$2"
|
|
636
805
|
local marker="$3"
|
|
806
|
+
local copied="${4:-true}"
|
|
637
807
|
local rel
|
|
638
808
|
rel="$(project_rel_path "$dest")"
|
|
639
809
|
local digest
|
|
640
810
|
digest="$(hash_file "$dest")"
|
|
641
811
|
MANAGED_RECORDS+=("$rel|$source_ref|$digest|$marker")
|
|
642
|
-
|
|
812
|
+
if [[ "$copied" == true ]]; then
|
|
813
|
+
unique_append "$dest" COPIED_PATHS
|
|
814
|
+
fi
|
|
643
815
|
}
|
|
644
816
|
|
|
645
817
|
record_agentic_event() {
|
|
@@ -679,8 +851,10 @@ write_file_with_agentic_marker() {
|
|
|
679
851
|
can_write_managed_file "$dest" || return 0
|
|
680
852
|
|
|
681
853
|
ensure_dir "$(dirname -- "$dest")"
|
|
682
|
-
|
|
854
|
+
local write_status
|
|
855
|
+
write_status="$(python3 - "$src" "$dest" "$source_ref" "$APP_REPO_LINK" "$(app_version_label)" <<'PY'
|
|
683
856
|
import json
|
|
857
|
+
import re
|
|
684
858
|
import sys
|
|
685
859
|
from pathlib import Path
|
|
686
860
|
|
|
@@ -688,6 +862,7 @@ src = Path(sys.argv[1])
|
|
|
688
862
|
dest = Path(sys.argv[2])
|
|
689
863
|
source_ref = sys.argv[3]
|
|
690
864
|
repo = sys.argv[4]
|
|
865
|
+
version = sys.argv[5]
|
|
691
866
|
text = src.read_text(encoding="utf-8")
|
|
692
867
|
suffix = dest.suffix.lower()
|
|
693
868
|
marker = f"Generated by agentic; source: {source_ref}; repository: {repo}"
|
|
@@ -697,12 +872,28 @@ def yaml_quote(value: str) -> str:
|
|
|
697
872
|
return json.dumps(value, ensure_ascii=False)
|
|
698
873
|
|
|
699
874
|
|
|
875
|
+
def existing_created_by() -> str:
|
|
876
|
+
if not dest.exists():
|
|
877
|
+
return version
|
|
878
|
+
try:
|
|
879
|
+
old = dest.read_text(encoding="utf-8")
|
|
880
|
+
except Exception:
|
|
881
|
+
return version
|
|
882
|
+
match = re.search(r"(?m)^ created_by:\s*(.+?)\s*$", old)
|
|
883
|
+
if not match:
|
|
884
|
+
return version
|
|
885
|
+
return match.group(1).strip().strip('"')
|
|
886
|
+
|
|
887
|
+
|
|
700
888
|
def markdown_with_marker(body: str) -> str:
|
|
889
|
+
created_by = existing_created_by()
|
|
701
890
|
block = (
|
|
702
891
|
"agentic:\n"
|
|
703
892
|
" generated_by: agentic\n"
|
|
704
893
|
f" source: {yaml_quote(source_ref)}\n"
|
|
705
894
|
f" repository: {yaml_quote(repo)}\n"
|
|
895
|
+
f" created_by: {yaml_quote(created_by)}\n"
|
|
896
|
+
f" updated_by: {yaml_quote(version)}\n"
|
|
706
897
|
)
|
|
707
898
|
if body.startswith("---\n"):
|
|
708
899
|
end = body.find("\n---", 4)
|
|
@@ -731,11 +922,6 @@ elif suffix == ".json":
|
|
|
731
922
|
data = json.loads(text)
|
|
732
923
|
if not isinstance(data, dict):
|
|
733
924
|
raise SystemExit(f"Cannot add agentic metadata to non-object JSON: {dest}")
|
|
734
|
-
data["_agentic"] = {
|
|
735
|
-
"generated_by": "agentic",
|
|
736
|
-
"source": source_ref,
|
|
737
|
-
"repository": repo,
|
|
738
|
-
}
|
|
739
925
|
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
740
926
|
elif suffix in {".ts", ".tsx", ".js", ".jsx", ".css"}:
|
|
741
927
|
output = commented(text, "//")
|
|
@@ -744,9 +930,23 @@ elif suffix in {".sh", ".toml", ".py", ".yml", ".yaml"}:
|
|
|
744
930
|
else:
|
|
745
931
|
output = commented(text, "#")
|
|
746
932
|
|
|
933
|
+
if dest.exists():
|
|
934
|
+
try:
|
|
935
|
+
if dest.read_text(encoding="utf-8") == output:
|
|
936
|
+
print("unchanged")
|
|
937
|
+
raise SystemExit(0)
|
|
938
|
+
except UnicodeDecodeError:
|
|
939
|
+
pass
|
|
940
|
+
|
|
747
941
|
dest.write_text(output, encoding="utf-8")
|
|
942
|
+
print("written")
|
|
748
943
|
PY
|
|
749
|
-
|
|
944
|
+
)"
|
|
945
|
+
if [[ "$write_status" == "unchanged" ]]; then
|
|
946
|
+
register_managed_file "$dest" "$source_ref" "internal" false
|
|
947
|
+
else
|
|
948
|
+
register_managed_file "$dest" "$source_ref" "internal"
|
|
949
|
+
fi
|
|
750
950
|
}
|
|
751
951
|
|
|
752
952
|
write_agentic_manifest() {
|
|
@@ -780,7 +980,8 @@ write_agentic_manifest() {
|
|
|
780
980
|
specs_csv="${SELECTED_SPECS[*]}"
|
|
781
981
|
IFS="$old_ifs"
|
|
782
982
|
|
|
783
|
-
|
|
983
|
+
local manifest_status
|
|
984
|
+
manifest_status="$(python3 - "$manifest" "$records_file" "$skipped_file" "$APP_REPO_LINK" "$REPO_ROOT" "$agent_os_csv" "$areas_csv" "$specs_csv" "$(app_version_label)" <<'PY'
|
|
784
985
|
import json
|
|
785
986
|
import sys
|
|
786
987
|
from datetime import datetime, timezone
|
|
@@ -794,37 +995,55 @@ repo_root = sys.argv[5]
|
|
|
794
995
|
agent_os = [x for x in sys.argv[6].split(",") if x]
|
|
795
996
|
areas = [x for x in sys.argv[7].split(",") if x]
|
|
796
997
|
specs = [x for x in sys.argv[8].split(",") if x]
|
|
998
|
+
app_version = sys.argv[9]
|
|
797
999
|
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
798
1000
|
|
|
799
1001
|
existing = {}
|
|
800
1002
|
created_at = now
|
|
1003
|
+
old_data = None
|
|
801
1004
|
if manifest.exists():
|
|
802
1005
|
try:
|
|
803
1006
|
old = json.loads(manifest.read_text(encoding="utf-8"))
|
|
1007
|
+
old_data = old
|
|
804
1008
|
created_at = old.get("created_at", created_at)
|
|
805
1009
|
for item in old.get("managed_files", []):
|
|
806
1010
|
if item.get("path"):
|
|
807
1011
|
existing[item["path"]] = item
|
|
808
1012
|
except Exception:
|
|
809
1013
|
existing = {}
|
|
1014
|
+
original_existing = json.loads(json.dumps(existing))
|
|
810
1015
|
|
|
811
1016
|
for line in records_file.read_text(encoding="utf-8").splitlines():
|
|
812
1017
|
if not line:
|
|
813
1018
|
continue
|
|
814
1019
|
path, source, digest, marker = (line.split("|", 3) + ["", "", "", ""])[:4]
|
|
1020
|
+
old_item = original_existing.get(path, {})
|
|
1021
|
+
old_updated_at = old_item.get("updated_at", now)
|
|
1022
|
+
if (
|
|
1023
|
+
old_item.get("source") == source
|
|
1024
|
+
and old_item.get("content_hash") == digest
|
|
1025
|
+
and old_item.get("marker") == marker
|
|
1026
|
+
):
|
|
1027
|
+
item_updated_at = old_updated_at
|
|
1028
|
+
else:
|
|
1029
|
+
item_updated_at = now
|
|
815
1030
|
existing[path] = {
|
|
816
1031
|
"path": path,
|
|
817
1032
|
"source": source,
|
|
818
1033
|
"content_hash": digest,
|
|
819
1034
|
"marker": marker,
|
|
820
|
-
"updated_at":
|
|
1035
|
+
"updated_at": item_updated_at,
|
|
821
1036
|
}
|
|
822
1037
|
|
|
823
1038
|
skipped = [x for x in skipped_file.read_text(encoding="utf-8").splitlines() if x]
|
|
1039
|
+
old_agentic = old_data.get("_agentic", {}) if isinstance(old_data, dict) else {}
|
|
1040
|
+
created_by = old_agentic.get("created_by", app_version)
|
|
824
1041
|
data = {
|
|
825
1042
|
"_agentic": {
|
|
826
1043
|
"generated_by": "agentic",
|
|
827
1044
|
"repository": repo_link,
|
|
1045
|
+
"created_by": created_by,
|
|
1046
|
+
"updated_by": app_version,
|
|
828
1047
|
},
|
|
829
1048
|
"version": 1,
|
|
830
1049
|
"created_at": created_at,
|
|
@@ -839,9 +1058,26 @@ data = {
|
|
|
839
1058
|
"managed_files": sorted(existing.values(), key=lambda x: x["path"]),
|
|
840
1059
|
"skipped_files": skipped,
|
|
841
1060
|
}
|
|
1061
|
+
|
|
1062
|
+
if old_data is not None:
|
|
1063
|
+
old_compare = json.loads(json.dumps(old_data))
|
|
1064
|
+
new_compare = json.loads(json.dumps(data))
|
|
1065
|
+
for payload in (old_compare, new_compare):
|
|
1066
|
+
payload.pop("updated_at", None)
|
|
1067
|
+
if isinstance(payload.get("_agentic"), dict):
|
|
1068
|
+
payload["_agentic"].pop("updated_by", None)
|
|
1069
|
+
if old_compare == new_compare:
|
|
1070
|
+
print("unchanged")
|
|
1071
|
+
raise SystemExit(0)
|
|
1072
|
+
|
|
842
1073
|
manifest.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
1074
|
+
print("written")
|
|
843
1075
|
PY
|
|
1076
|
+
)"
|
|
844
1077
|
rm -f "$records_file" "$skipped_file"
|
|
1078
|
+
if [[ "$manifest_status" == "unchanged" ]]; then
|
|
1079
|
+
return
|
|
1080
|
+
fi
|
|
845
1081
|
unique_append "$manifest" COPIED_PATHS
|
|
846
1082
|
}
|
|
847
1083
|
|
|
@@ -981,9 +1217,10 @@ copy_dir_contents() {
|
|
|
981
1217
|
|
|
982
1218
|
local event kind value events_file
|
|
983
1219
|
events_file="$(mktemp "${TMPDIR:-/tmp}/agentic-copy-events.XXXXXX")"
|
|
984
|
-
python3 - "$src" "$dest" "$REPO_ROOT" "$PROJECT_DIR" "$(project_manifest_path)" "$APP_REPO_LINK" > "$events_file" <<'PY'
|
|
1220
|
+
python3 - "$src" "$dest" "$REPO_ROOT" "$PROJECT_DIR" "$(project_manifest_path)" "$APP_REPO_LINK" "$(app_version_label)" > "$events_file" <<'PY'
|
|
985
1221
|
import hashlib
|
|
986
1222
|
import json
|
|
1223
|
+
import re
|
|
987
1224
|
import sys
|
|
988
1225
|
from pathlib import Path
|
|
989
1226
|
|
|
@@ -993,6 +1230,7 @@ repo_root = Path(sys.argv[3])
|
|
|
993
1230
|
project_dir = Path(sys.argv[4])
|
|
994
1231
|
manifest = Path(sys.argv[5])
|
|
995
1232
|
repo = sys.argv[6]
|
|
1233
|
+
version = sys.argv[7]
|
|
996
1234
|
|
|
997
1235
|
|
|
998
1236
|
def emit(kind: str, value: str) -> None:
|
|
@@ -1015,7 +1253,7 @@ if manifest.exists():
|
|
|
1015
1253
|
for item in data.get("managed_files", []):
|
|
1016
1254
|
rel = item.get("path")
|
|
1017
1255
|
if rel:
|
|
1018
|
-
managed[rel] = item
|
|
1256
|
+
managed[rel] = item
|
|
1019
1257
|
except Exception:
|
|
1020
1258
|
managed = {}
|
|
1021
1259
|
|
|
@@ -1038,12 +1276,28 @@ def yaml_quote(value: str) -> str:
|
|
|
1038
1276
|
return json.dumps(value, ensure_ascii=False)
|
|
1039
1277
|
|
|
1040
1278
|
|
|
1041
|
-
def
|
|
1279
|
+
def existing_created_by(target: Path) -> str:
|
|
1280
|
+
if not target.exists():
|
|
1281
|
+
return version
|
|
1282
|
+
try:
|
|
1283
|
+
old = target.read_text(encoding="utf-8")
|
|
1284
|
+
except Exception:
|
|
1285
|
+
return version
|
|
1286
|
+
match = re.search(r"(?m)^ created_by:\s*(.+?)\s*$", old)
|
|
1287
|
+
if not match:
|
|
1288
|
+
return version
|
|
1289
|
+
return match.group(1).strip().strip('"')
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def markdown_with_marker(body: str, source_ref: str, target: Path) -> str:
|
|
1293
|
+
created_by = existing_created_by(target)
|
|
1042
1294
|
block = (
|
|
1043
1295
|
"agentic:\n"
|
|
1044
1296
|
" generated_by: agentic\n"
|
|
1045
1297
|
f" source: {yaml_quote(source_ref)}\n"
|
|
1046
1298
|
f" repository: {yaml_quote(repo)}\n"
|
|
1299
|
+
f" created_by: {yaml_quote(created_by)}\n"
|
|
1300
|
+
f" updated_by: {yaml_quote(version)}\n"
|
|
1047
1301
|
)
|
|
1048
1302
|
if body.startswith("---\n"):
|
|
1049
1303
|
end = body.find("\n---", 4)
|
|
@@ -1066,16 +1320,11 @@ def add_marker(file_path: Path, target: Path, source_ref: str) -> str:
|
|
|
1066
1320
|
text = file_path.read_text(encoding="utf-8")
|
|
1067
1321
|
suffix = target.suffix.lower()
|
|
1068
1322
|
if suffix == ".md":
|
|
1069
|
-
return markdown_with_marker(text, source_ref)
|
|
1323
|
+
return markdown_with_marker(text, source_ref, target)
|
|
1070
1324
|
if suffix == ".json":
|
|
1071
1325
|
data = json.loads(text)
|
|
1072
1326
|
if not isinstance(data, dict):
|
|
1073
1327
|
raise SystemExit(f"Cannot add agentic metadata to non-object JSON: {target}")
|
|
1074
|
-
data["_agentic"] = {
|
|
1075
|
-
"generated_by": "agentic",
|
|
1076
|
-
"source": source_ref,
|
|
1077
|
-
"repository": repo,
|
|
1078
|
-
}
|
|
1079
1328
|
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
1080
1329
|
if suffix in {".ts", ".tsx", ".js", ".jsx", ".css"}:
|
|
1081
1330
|
return commented(text, "//", source_ref)
|
|
@@ -1097,15 +1346,27 @@ for file_path in sorted(p for p in src.rglob("*") if p.is_file()):
|
|
|
1097
1346
|
emit("WARN", f"Skipping unmanaged target on rerun: {project_rel}")
|
|
1098
1347
|
emit("SKIP", project_rel)
|
|
1099
1348
|
continue
|
|
1100
|
-
|
|
1349
|
+
managed_item = managed.get(project_rel, {})
|
|
1350
|
+
if managed_item.get("marker") == "config":
|
|
1351
|
+
continue
|
|
1352
|
+
expected_hash = managed_item.get("content_hash", "")
|
|
1101
1353
|
if target.exists() and expected_hash and sha256(target) != expected_hash:
|
|
1102
1354
|
emit("WARN", f"Skipping user-modified managed file: {project_rel}")
|
|
1103
1355
|
emit("SKIP", project_rel)
|
|
1104
1356
|
continue
|
|
1105
1357
|
|
|
1358
|
+
output = add_marker(file_path, target, source_ref)
|
|
1106
1359
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
1107
1360
|
emit("DIR", str(target.parent))
|
|
1108
|
-
target.
|
|
1361
|
+
if target.exists():
|
|
1362
|
+
try:
|
|
1363
|
+
if target.read_text(encoding="utf-8") == output:
|
|
1364
|
+
digest = sha256(target)
|
|
1365
|
+
emit("RECORD", f"{project_rel}|{source_ref}|{digest}|internal")
|
|
1366
|
+
continue
|
|
1367
|
+
except UnicodeDecodeError:
|
|
1368
|
+
pass
|
|
1369
|
+
target.write_text(output, encoding="utf-8")
|
|
1109
1370
|
digest = sha256(target)
|
|
1110
1371
|
emit("RECORD", f"{project_rel}|{source_ref}|{digest}|internal")
|
|
1111
1372
|
emit("COPIED", str(target))
|
|
@@ -1156,7 +1417,8 @@ write_json_file_with_agentic_metadata() {
|
|
|
1156
1417
|
|
|
1157
1418
|
can_write_managed_file "$dest" || return 0
|
|
1158
1419
|
ensure_dir "$(dirname -- "$dest")"
|
|
1159
|
-
|
|
1420
|
+
local write_status
|
|
1421
|
+
write_status="$(python3 - "$dest" "$source_ref" "$APP_REPO_LINK" "$CONTEXT7_API_KEY" "$python_body" "$(app_version_label)" <<'PY'
|
|
1160
1422
|
import json
|
|
1161
1423
|
import sys
|
|
1162
1424
|
from pathlib import Path
|
|
@@ -1166,11 +1428,15 @@ source_ref = sys.argv[2]
|
|
|
1166
1428
|
repo = sys.argv[3]
|
|
1167
1429
|
context7_api_key = sys.argv[4]
|
|
1168
1430
|
body = sys.argv[5]
|
|
1431
|
+
version = sys.argv[6]
|
|
1169
1432
|
|
|
1170
1433
|
data = {}
|
|
1434
|
+
created_by = version
|
|
1171
1435
|
if path.exists():
|
|
1172
1436
|
try:
|
|
1173
1437
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
1438
|
+
if isinstance(data, dict):
|
|
1439
|
+
created_by = data.get("_agentic", {}).get("created_by", version)
|
|
1174
1440
|
except Exception:
|
|
1175
1441
|
data = {}
|
|
1176
1442
|
if not isinstance(data, dict):
|
|
@@ -1182,14 +1448,28 @@ namespace = {
|
|
|
1182
1448
|
}
|
|
1183
1449
|
exec(body, namespace)
|
|
1184
1450
|
data = namespace["data"]
|
|
1185
|
-
data
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1451
|
+
metadata = data.setdefault("_agentic", {})
|
|
1452
|
+
metadata["generated_by"] = "agentic"
|
|
1453
|
+
metadata["repository"] = repo
|
|
1454
|
+
metadata["created_by"] = created_by
|
|
1455
|
+
metadata["updated_by"] = version
|
|
1456
|
+
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
1457
|
+
if path.exists():
|
|
1458
|
+
try:
|
|
1459
|
+
if path.read_text(encoding="utf-8") == output:
|
|
1460
|
+
print("unchanged")
|
|
1461
|
+
raise SystemExit(0)
|
|
1462
|
+
except UnicodeDecodeError:
|
|
1463
|
+
pass
|
|
1190
1464
|
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
1465
|
+
print("written")
|
|
1191
1466
|
PY
|
|
1192
|
-
|
|
1467
|
+
)"
|
|
1468
|
+
if [[ "$write_status" == "unchanged" ]]; then
|
|
1469
|
+
register_managed_file "$dest" "$source_ref" "internal" false
|
|
1470
|
+
else
|
|
1471
|
+
register_managed_file "$dest" "$source_ref" "internal"
|
|
1472
|
+
fi
|
|
1193
1473
|
}
|
|
1194
1474
|
|
|
1195
1475
|
write_json_config_file() {
|
|
@@ -1205,7 +1485,8 @@ write_json_config_file() {
|
|
|
1205
1485
|
|
|
1206
1486
|
can_write_managed_file "$dest" || return 0
|
|
1207
1487
|
ensure_dir "$(dirname -- "$dest")"
|
|
1208
|
-
|
|
1488
|
+
local write_status
|
|
1489
|
+
write_status="$(python3 - "$dest" "$CONTEXT7_API_KEY" "$python_body" <<'PY'
|
|
1209
1490
|
import json
|
|
1210
1491
|
import sys
|
|
1211
1492
|
from pathlib import Path
|
|
@@ -1229,9 +1510,62 @@ namespace = {
|
|
|
1229
1510
|
}
|
|
1230
1511
|
exec(body, namespace)
|
|
1231
1512
|
data = namespace["data"]
|
|
1232
|
-
|
|
1513
|
+
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
1514
|
+
if path.exists():
|
|
1515
|
+
try:
|
|
1516
|
+
if path.read_text(encoding="utf-8") == output:
|
|
1517
|
+
print("unchanged")
|
|
1518
|
+
raise SystemExit(0)
|
|
1519
|
+
except UnicodeDecodeError:
|
|
1520
|
+
pass
|
|
1521
|
+
path.write_text(output, encoding="utf-8")
|
|
1522
|
+
print("written")
|
|
1233
1523
|
PY
|
|
1234
|
-
|
|
1524
|
+
)"
|
|
1525
|
+
if [[ "$write_status" == "unchanged" ]]; then
|
|
1526
|
+
register_managed_file "$dest" "$source_ref" "config" false
|
|
1527
|
+
else
|
|
1528
|
+
register_managed_file "$dest" "$source_ref" "config"
|
|
1529
|
+
fi
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
write_text_config_file() {
|
|
1533
|
+
local dest="$1"
|
|
1534
|
+
local source_ref="$2"
|
|
1535
|
+
local content="$3"
|
|
1536
|
+
|
|
1537
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1538
|
+
log "DRY-RUN write text config file $dest"
|
|
1539
|
+
unique_append "$dest" COPIED_PATHS
|
|
1540
|
+
return
|
|
1541
|
+
fi
|
|
1542
|
+
|
|
1543
|
+
can_write_managed_file "$dest" || return 0
|
|
1544
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
1545
|
+
|
|
1546
|
+
local write_status
|
|
1547
|
+
write_status="$(python3 - "$dest" "$content" <<'PY'
|
|
1548
|
+
import sys
|
|
1549
|
+
from pathlib import Path
|
|
1550
|
+
|
|
1551
|
+
path = Path(sys.argv[1])
|
|
1552
|
+
content = sys.argv[2]
|
|
1553
|
+
if path.exists():
|
|
1554
|
+
try:
|
|
1555
|
+
if path.read_text(encoding="utf-8") == content:
|
|
1556
|
+
print("unchanged")
|
|
1557
|
+
raise SystemExit(0)
|
|
1558
|
+
except UnicodeDecodeError:
|
|
1559
|
+
pass
|
|
1560
|
+
path.write_text(content, encoding="utf-8")
|
|
1561
|
+
print("written")
|
|
1562
|
+
PY
|
|
1563
|
+
)"
|
|
1564
|
+
if [[ "$write_status" == "unchanged" ]]; then
|
|
1565
|
+
register_managed_file "$dest" "$source_ref" "config" false
|
|
1566
|
+
else
|
|
1567
|
+
register_managed_file "$dest" "$source_ref" "config"
|
|
1568
|
+
fi
|
|
1235
1569
|
}
|
|
1236
1570
|
|
|
1237
1571
|
write_context7_opencode_config() {
|
|
@@ -1265,22 +1599,32 @@ if context7_api_key:
|
|
|
1265
1599
|
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1266
1600
|
mcp["context7"] = context7
|
|
1267
1601
|
'
|
|
1268
|
-
|
|
1602
|
+
write_json_config_file "$dest" "generated:context7-opencode-legacy-config" "$body"
|
|
1269
1603
|
}
|
|
1270
1604
|
|
|
1271
1605
|
write_context7_codex_config() {
|
|
1272
1606
|
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1273
|
-
local
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1607
|
+
local body
|
|
1608
|
+
body="$(python3 - "$dest" "$CONTEXT7_API_KEY" <<'PY'
|
|
1609
|
+
import re
|
|
1610
|
+
import sys
|
|
1611
|
+
from pathlib import Path
|
|
1612
|
+
|
|
1613
|
+
path = Path(sys.argv[1])
|
|
1614
|
+
api_key = sys.argv[2]
|
|
1615
|
+
text = path.read_text(encoding="utf-8") if path.exists() else ""
|
|
1616
|
+
text = re.sub(r"(?ms)^\[mcp_servers\.context7\]\n.*?(?=^\[|\Z)", "", text).strip()
|
|
1617
|
+
block = '[mcp_servers.context7]\nurl = "https://mcp.context7.com/mcp"\n'
|
|
1618
|
+
if api_key:
|
|
1619
|
+
escaped = api_key.replace("\\", "\\\\").replace('"', '\\"')
|
|
1620
|
+
block += f'http_headers = {{ "CONTEXT7_API_KEY" = "{escaped}" }}\n'
|
|
1621
|
+
if text:
|
|
1622
|
+
print(block + "\n" + text.rstrip() + "\n", end="")
|
|
1623
|
+
else:
|
|
1624
|
+
print(block, end="")
|
|
1625
|
+
PY
|
|
1626
|
+
)"
|
|
1627
|
+
write_text_config_file "$dest" "generated:context7-codex-config" "$body"
|
|
1284
1628
|
}
|
|
1285
1629
|
|
|
1286
1630
|
write_context7_claude_config() {
|
|
@@ -1314,6 +1658,37 @@ mcp_servers["context7"] = context7
|
|
|
1314
1658
|
write_json_config_file "$dest" "generated:context7-cursor-config" "$body"
|
|
1315
1659
|
}
|
|
1316
1660
|
|
|
1661
|
+
|
|
1662
|
+
write_context7_kilocode_config() {
|
|
1663
|
+
local dest="$PROJECT_DIR/.kilocode/mcp.json"
|
|
1664
|
+
local body
|
|
1665
|
+
body='
|
|
1666
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1667
|
+
context7 = {
|
|
1668
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1669
|
+
}
|
|
1670
|
+
if context7_api_key:
|
|
1671
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1672
|
+
mcp_servers["context7"] = context7
|
|
1673
|
+
'
|
|
1674
|
+
write_json_config_file "$dest" "generated:context7-kilocode-config" "$body"
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
write_context7_antigravity_config() {
|
|
1678
|
+
local dest="$HOME/.gemini/antigravity/mcp_config.json"
|
|
1679
|
+
local body
|
|
1680
|
+
body='
|
|
1681
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1682
|
+
context7 = {
|
|
1683
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1684
|
+
}
|
|
1685
|
+
if context7_api_key:
|
|
1686
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1687
|
+
mcp_servers["context7"] = context7
|
|
1688
|
+
'
|
|
1689
|
+
write_json_config_file "$dest" "generated:context7-antigravity-config" "$body"
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1317
1692
|
write_context7_gemini_config() {
|
|
1318
1693
|
local dest="$PROJECT_DIR/.gemini/settings.json"
|
|
1319
1694
|
local body
|
|
@@ -1329,19 +1704,190 @@ mcp_servers["context7"] = context7
|
|
|
1329
1704
|
write_json_config_file "$dest" "generated:context7-gemini-config" "$body"
|
|
1330
1705
|
}
|
|
1331
1706
|
|
|
1707
|
+
write_mempalace_opencode_config() {
|
|
1708
|
+
local dest="$1"
|
|
1709
|
+
local body
|
|
1710
|
+
body='
|
|
1711
|
+
mcp = data.setdefault("mcp", {})
|
|
1712
|
+
mcp["mempalace"] = {"type": "local", "command": ["mempalace-mcp"]}
|
|
1713
|
+
'
|
|
1714
|
+
write_json_config_file "$dest" "generated:mempalace-opencode-config" "$body"
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
write_mempalace_codex_config() {
|
|
1718
|
+
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1719
|
+
local body
|
|
1720
|
+
body="$(python3 - "$dest" <<'PYCODE'
|
|
1721
|
+
import re
|
|
1722
|
+
import pathlib
|
|
1723
|
+
import sys
|
|
1724
|
+
|
|
1725
|
+
path = pathlib.Path(sys.argv[1])
|
|
1726
|
+
text = path.read_text(encoding='utf-8') if path.exists() else ''
|
|
1727
|
+
block = "[mcp_servers.mempalace]\ncommand = \"mempalace-mcp\"\n"
|
|
1728
|
+
text = re.sub(r"(?ms)^\[mcp_servers\.mempalace\]\n.*?(?=^\[|\Z)", "", text).strip()
|
|
1729
|
+
if text:
|
|
1730
|
+
print(text.rstrip() + "\n\n" + block, end="")
|
|
1731
|
+
else:
|
|
1732
|
+
print(block, end="")
|
|
1733
|
+
PYCODE
|
|
1734
|
+
)"
|
|
1735
|
+
write_text_config_file "$dest" "generated:mempalace-codex-config" "$body"
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
write_mempalace_generic_json_config() {
|
|
1739
|
+
local dest="$1"
|
|
1740
|
+
local marker="$2"
|
|
1741
|
+
local body
|
|
1742
|
+
body='
|
|
1743
|
+
servers = data.setdefault("mcpServers", {})
|
|
1744
|
+
servers["mempalace"] = {"command": "mempalace-mcp"}
|
|
1745
|
+
'
|
|
1746
|
+
write_json_config_file "$dest" "$marker" "$body"
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
print_mempalace_project_setup_instructions() {
|
|
1750
|
+
log "MemPalace setup instructions for target project: $PROJECT_DIR"
|
|
1751
|
+
out "1) Ensure Python is installed and available in PATH."
|
|
1752
|
+
out "2) Install MemPalace:"
|
|
1753
|
+
out " pip install mempalace"
|
|
1754
|
+
out "3) Initialize project-local MemPalace cache:"
|
|
1755
|
+
out " mempalace init \"$PROJECT_DIR\" --yes --auto-mine"
|
|
1756
|
+
out "4) Index existing project memory:"
|
|
1757
|
+
out " # optional if --auto-mine was skipped"
|
|
1758
|
+
out " mempalace mine \"$PROJECT_DIR\""
|
|
1759
|
+
out "5) Verify in your IDE/agent that MemPalace MCP tools are connected."
|
|
1760
|
+
out "Note: Ollama at localhost:11434 is optional; MemPalace can run heuristics-only without it."
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
setup_mempalace_for_agentic_opencode() {
|
|
1764
|
+
local step_prefix="MemPalace setup"
|
|
1765
|
+
|
|
1766
|
+
log "$step_prefix [1/4] Checking Python availability"
|
|
1767
|
+
if ! command -v python3 >/dev/null 2>&1 && ! command -v python >/dev/null 2>&1; then
|
|
1768
|
+
warn "Python is not installed. Install Python 3 first, then run: pip install mempalace"
|
|
1769
|
+
warn "Install help: https://www.python.org/downloads/"
|
|
1770
|
+
return 1
|
|
1771
|
+
fi
|
|
1772
|
+
log "$step_prefix [1/4] Python check passed"
|
|
1773
|
+
|
|
1774
|
+
log "$step_prefix [2/4] Checking pip availability"
|
|
1775
|
+
local pip_bin
|
|
1776
|
+
if ! pip_bin="$(pip_command)"; then
|
|
1777
|
+
warn "pip is not available. Install pip for Python 3, then run: pip install mempalace"
|
|
1778
|
+
return 1
|
|
1779
|
+
fi
|
|
1780
|
+
log "$step_prefix [2/4] pip check passed"
|
|
1781
|
+
|
|
1782
|
+
log "$step_prefix [3/4] Installing mempalace package"
|
|
1783
|
+
if $pip_bin install mempalace >/dev/null 2>&1; then
|
|
1784
|
+
log "MemPalace package installed via '$pip_bin install mempalace'"
|
|
1785
|
+
else
|
|
1786
|
+
warn "Unable to auto-install mempalace via pip; continuing with manual setup instructions"
|
|
1787
|
+
print_mempalace_project_setup_instructions
|
|
1788
|
+
return 1
|
|
1789
|
+
fi
|
|
1790
|
+
|
|
1791
|
+
log "$step_prefix [4/4] Initializing project memory at $PROJECT_DIR"
|
|
1792
|
+
if command -v mempalace >/dev/null 2>&1; then
|
|
1793
|
+
if mempalace init "$PROJECT_DIR" --yes --auto-mine >/dev/null 2>&1; then
|
|
1794
|
+
log "MemPalace init completed"
|
|
1795
|
+
else
|
|
1796
|
+
warn "Failed: mempalace init \"$PROJECT_DIR\" --yes --auto-mine"
|
|
1797
|
+
fi
|
|
1798
|
+
if mempalace mine "$PROJECT_DIR" >/dev/null 2>&1; then
|
|
1799
|
+
log "MemPalace mine completed"
|
|
1800
|
+
else
|
|
1801
|
+
warn "Failed: mempalace mine \"$PROJECT_DIR\""
|
|
1802
|
+
fi
|
|
1803
|
+
log "$step_prefix [4/4] Initialization step finished"
|
|
1804
|
+
else
|
|
1805
|
+
warn "mempalace command is unavailable after install; please run setup manually"
|
|
1806
|
+
print_mempalace_project_setup_instructions
|
|
1807
|
+
return 1
|
|
1808
|
+
fi
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
configure_mempalace_if_needed() {
|
|
1812
|
+
if ! selected_agent_os_contains "opencode" \
|
|
1813
|
+
&& ! selected_agent_os_contains "codex" \
|
|
1814
|
+
&& ! selected_agent_os_contains "claude" \
|
|
1815
|
+
&& ! selected_agent_os_contains "cursor" \
|
|
1816
|
+
&& ! selected_agent_os_contains "gemini" \
|
|
1817
|
+
&& ! selected_agent_os_contains "kilocode" \
|
|
1818
|
+
&& ! selected_agent_os_contains "antigravity"; then
|
|
1819
|
+
return
|
|
1820
|
+
fi
|
|
1821
|
+
|
|
1822
|
+
local enable_mempalace="N"
|
|
1823
|
+
if [[ -n "${AGENTIC_ENABLE_MEMPALACE:-}" ]]; then
|
|
1824
|
+
enable_mempalace="$(trim "${AGENTIC_ENABLE_MEMPALACE}")"
|
|
1825
|
+
elif is_interactive_terminal && [[ -z "${AGENTIC_TEST_SOURCE_AGENTIC:-}" ]]; then
|
|
1826
|
+
read -r -p "Enable MemPalace MCP memory integration? [y/N]: " enable_mempalace
|
|
1827
|
+
enable_mempalace="$(trim "${enable_mempalace:-n}")"
|
|
1828
|
+
if [[ -z "$enable_mempalace" ]]; then enable_mempalace="n"; fi
|
|
1829
|
+
fi
|
|
1830
|
+
if [[ "$enable_mempalace" =~ ^[Nn]$ ]]; then
|
|
1831
|
+
log "Skipped MemPalace MCP configuration"
|
|
1832
|
+
return
|
|
1833
|
+
fi
|
|
1834
|
+
|
|
1835
|
+
if selected_agent_os_contains "opencode"; then
|
|
1836
|
+
setup_mempalace_for_agentic_opencode || true
|
|
1837
|
+
else
|
|
1838
|
+
print_mempalace_project_setup_instructions
|
|
1839
|
+
fi
|
|
1840
|
+
|
|
1841
|
+
if command -v mempalace-mcp >/dev/null 2>&1; then
|
|
1842
|
+
log "MemPalace MCP binary found: mempalace-mcp"
|
|
1843
|
+
else
|
|
1844
|
+
warn "mempalace-mcp is unavailable; install/repair MemPalace and re-run setup"
|
|
1845
|
+
fi
|
|
1846
|
+
|
|
1847
|
+
if selected_agent_os_contains "opencode"; then
|
|
1848
|
+
write_mempalace_opencode_config "$PROJECT_DIR/opencode.json"
|
|
1849
|
+
write_mempalace_opencode_config "$PROJECT_DIR/.opencode/opencode.json"
|
|
1850
|
+
fi
|
|
1851
|
+
if selected_agent_os_contains "codex"; then write_mempalace_codex_config; fi
|
|
1852
|
+
if selected_agent_os_contains "claude"; then
|
|
1853
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.mcp.json" "generated:mempalace-claude-config"
|
|
1854
|
+
fi
|
|
1855
|
+
if selected_agent_os_contains "cursor"; then
|
|
1856
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.cursor/mcp.json" "generated:mempalace-cursor-config"
|
|
1857
|
+
fi
|
|
1858
|
+
if selected_agent_os_contains "gemini"; then
|
|
1859
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.gemini/settings.json" "generated:mempalace-gemini-config"
|
|
1860
|
+
fi
|
|
1861
|
+
if selected_agent_os_contains "kilocode"; then
|
|
1862
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.kilocode/mcp.json" "generated:mempalace-kilocode-config"
|
|
1863
|
+
fi
|
|
1864
|
+
if selected_agent_os_contains "antigravity"; then
|
|
1865
|
+
write_mempalace_generic_json_config "$HOME/.gemini/antigravity/mcp_config.json" "generated:mempalace-antigravity-config"
|
|
1866
|
+
fi
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1332
1869
|
configure_context7_if_needed() {
|
|
1333
1870
|
if ! selected_agent_os_contains "opencode" \
|
|
1334
1871
|
&& ! selected_agent_os_contains "codex" \
|
|
1335
1872
|
&& ! selected_agent_os_contains "claude" \
|
|
1336
1873
|
&& ! selected_agent_os_contains "cursor" \
|
|
1337
|
-
&& ! selected_agent_os_contains "gemini"
|
|
1874
|
+
&& ! selected_agent_os_contains "gemini" \
|
|
1875
|
+
&& ! selected_agent_os_contains "kilocode" \
|
|
1876
|
+
&& ! selected_agent_os_contains "antigravity"; then
|
|
1338
1877
|
return
|
|
1339
1878
|
fi
|
|
1340
1879
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
read -r -p "Enable Context7 MCP configuration? [y/N]: " enable_context7
|
|
1880
|
+
local enable_context7="${AGENTIC_ENABLE_CONTEXT7:-}"
|
|
1881
|
+
if [[ -n "$enable_context7" ]]; then
|
|
1344
1882
|
enable_context7="$(trim "$enable_context7")"
|
|
1883
|
+
fi
|
|
1884
|
+
|
|
1885
|
+
if is_interactive_terminal; then
|
|
1886
|
+
local answer
|
|
1887
|
+
if [[ -z "$enable_context7" ]]; then
|
|
1888
|
+
read -r -p "Enable Context7 MCP configuration? [y/N]: " enable_context7
|
|
1889
|
+
enable_context7="$(trim "$enable_context7")"
|
|
1890
|
+
fi
|
|
1345
1891
|
if [[ ! "$enable_context7" =~ ^[Yy]$ ]]; then
|
|
1346
1892
|
log "Context7 MCP configuration disabled"
|
|
1347
1893
|
return
|
|
@@ -1376,6 +1922,14 @@ configure_context7_if_needed() {
|
|
|
1376
1922
|
if selected_agent_os_contains "gemini"; then
|
|
1377
1923
|
write_context7_gemini_config
|
|
1378
1924
|
fi
|
|
1925
|
+
|
|
1926
|
+
if selected_agent_os_contains "kilocode"; then
|
|
1927
|
+
write_context7_kilocode_config
|
|
1928
|
+
fi
|
|
1929
|
+
|
|
1930
|
+
if selected_agent_os_contains "antigravity"; then
|
|
1931
|
+
write_context7_antigravity_config
|
|
1932
|
+
fi
|
|
1379
1933
|
}
|
|
1380
1934
|
|
|
1381
1935
|
write_default_opencode_plugin_config() {
|
|
@@ -1415,9 +1969,34 @@ configure_opencode_plugins_if_needed() {
|
|
|
1415
1969
|
return
|
|
1416
1970
|
fi
|
|
1417
1971
|
|
|
1418
|
-
local
|
|
1419
|
-
|
|
1420
|
-
|
|
1972
|
+
local plugin_options=("telegram-opencode-notifier" "llm-quota-checker")
|
|
1973
|
+
local selected_plugins=()
|
|
1974
|
+
local use_fzf_plugins=false
|
|
1975
|
+
if fzf_available; then
|
|
1976
|
+
use_fzf_plugins=true
|
|
1977
|
+
elif ensure_fzf_or_fallback; then
|
|
1978
|
+
use_fzf_plugins=true
|
|
1979
|
+
fi
|
|
1980
|
+
|
|
1981
|
+
if [[ "$use_fzf_plugins" == true ]]; then
|
|
1982
|
+
readlines selected_plugins < <(choose_multi_fzf_strict "Select optional OpenCode plugin(s):" "${plugin_options[@]}")
|
|
1983
|
+
else
|
|
1984
|
+
local selected_plugins_output
|
|
1985
|
+
selected_plugins_output="$(choose_multi_by_index "Select optional OpenCode plugin(s):" "${plugin_options[@]}")"
|
|
1986
|
+
readlines selected_plugins <<< "$selected_plugins_output"
|
|
1987
|
+
fi
|
|
1988
|
+
|
|
1989
|
+
local enable_telegram="n" telegram_token telegram_chat enable_model_checker="n"
|
|
1990
|
+
local selected_plugin
|
|
1991
|
+
for selected_plugin in "${selected_plugins[@]}"; do
|
|
1992
|
+
selected_plugin="$(trim "$selected_plugin")"
|
|
1993
|
+
[[ -z "$selected_plugin" ]] && continue
|
|
1994
|
+
case "$selected_plugin" in
|
|
1995
|
+
telegram-opencode-notifier) enable_telegram="y" ;;
|
|
1996
|
+
llm-quota-checker) enable_model_checker="y" ;;
|
|
1997
|
+
esac
|
|
1998
|
+
done
|
|
1999
|
+
|
|
1421
2000
|
telegram_token=""
|
|
1422
2001
|
telegram_chat=""
|
|
1423
2002
|
if [[ "$enable_telegram" =~ ^[Yy]$ ]]; then
|
|
@@ -1427,9 +2006,6 @@ configure_opencode_plugins_if_needed() {
|
|
|
1427
2006
|
telegram_chat="$(trim "$telegram_chat")"
|
|
1428
2007
|
fi
|
|
1429
2008
|
|
|
1430
|
-
read -r -p "Enable OpenCode model checker plugin? [y/N]: " enable_model_checker
|
|
1431
|
-
enable_model_checker="$(trim "$enable_model_checker")"
|
|
1432
|
-
|
|
1433
2009
|
python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" "$telegram_token" "$telegram_chat" "$enable_model_checker" <<'PY'
|
|
1434
2010
|
import json
|
|
1435
2011
|
import sys
|
|
@@ -1556,7 +2132,7 @@ build_header() {
|
|
|
1556
2132
|
{
|
|
1557
2133
|
echo "# Agentic Project Guidelines"
|
|
1558
2134
|
echo
|
|
1559
|
-
echo "Generated by $SCRIPT_NAME
|
|
2135
|
+
echo "Generated by $SCRIPT_NAME."
|
|
1560
2136
|
echo
|
|
1561
2137
|
echo "## Installation Context"
|
|
1562
2138
|
echo "- Agent OS targets: ${SELECTED_AGENT_OS[*]}"
|
|
@@ -1610,11 +2186,31 @@ append_root_agents_template() {
|
|
|
1610
2186
|
|
|
1611
2187
|
generate_agents_md() {
|
|
1612
2188
|
local project_dir="$1"
|
|
1613
|
-
local
|
|
2189
|
+
local outputs=()
|
|
2190
|
+
local needs_root=false
|
|
2191
|
+
local agent_os
|
|
2192
|
+
|
|
2193
|
+
if selected_agent_os_contains "opencode"; then
|
|
2194
|
+
unique_append "$project_dir/.opencode/AGENTS.md" outputs
|
|
2195
|
+
fi
|
|
2196
|
+
|
|
2197
|
+
for agent_os in "${SELECTED_AGENT_OS[@]}"; do
|
|
2198
|
+
if [[ "$agent_os" != "opencode" ]]; then
|
|
2199
|
+
needs_root=true
|
|
2200
|
+
break
|
|
2201
|
+
fi
|
|
2202
|
+
done
|
|
2203
|
+
|
|
2204
|
+
if [[ "$needs_root" == true ]] || ! selected_agent_os_contains "opencode"; then
|
|
2205
|
+
unique_append "$project_dir/AGENTS.md" outputs
|
|
2206
|
+
fi
|
|
1614
2207
|
|
|
1615
2208
|
if [[ "$DRY_RUN" == true ]]; then
|
|
1616
|
-
|
|
1617
|
-
|
|
2209
|
+
local dry_run_out
|
|
2210
|
+
for dry_run_out in "${outputs[@]}"; do
|
|
2211
|
+
log "DRY-RUN generate $dry_run_out"
|
|
2212
|
+
unique_append "$dry_run_out" COPIED_PATHS
|
|
2213
|
+
done
|
|
1618
2214
|
return
|
|
1619
2215
|
fi
|
|
1620
2216
|
|
|
@@ -1629,7 +2225,10 @@ generate_agents_md() {
|
|
|
1629
2225
|
append_specialization_template "$tmp" "$spec_key"
|
|
1630
2226
|
done
|
|
1631
2227
|
|
|
1632
|
-
|
|
2228
|
+
local out
|
|
2229
|
+
for out in "${outputs[@]}"; do
|
|
2230
|
+
write_file_with_agentic_marker "$tmp" "$out" "generated:AGENTS.md"
|
|
2231
|
+
done
|
|
1633
2232
|
rm -f "$tmp"
|
|
1634
2233
|
}
|
|
1635
2234
|
|
|
@@ -1700,43 +2299,304 @@ validate_inputs() {
|
|
|
1700
2299
|
}
|
|
1701
2300
|
|
|
1702
2301
|
print_report() {
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
2302
|
+
out
|
|
2303
|
+
out "=== Installation report ===" "$COLOR_HEADER"
|
|
2304
|
+
out "Agentic version: $(app_version_label)"
|
|
2305
|
+
out "Project dir: $PROJECT_DIR"
|
|
2306
|
+
out "Knowledge base repo: $REPO_ROOT"
|
|
2307
|
+
out "Config file: $APP_CONFIG_FILE"
|
|
2308
|
+
out "Agent OS targets: ${SELECTED_AGENT_OS[*]}"
|
|
2309
|
+
out "Areas: ${SELECTED_AREAS[*]}"
|
|
2310
|
+
out "Specializations: ${SELECTED_SPECS[*]}"
|
|
2311
|
+
|
|
2312
|
+
out
|
|
2313
|
+
out "Created directories:"
|
|
1714
2314
|
if [[ "${#CREATED_PATHS[@]}" -eq 0 ]]; then
|
|
1715
|
-
|
|
2315
|
+
out "- (none)"
|
|
1716
2316
|
else
|
|
1717
|
-
|
|
2317
|
+
local created_path
|
|
2318
|
+
for created_path in "${CREATED_PATHS[@]}"; do
|
|
2319
|
+
out "- $created_path"
|
|
2320
|
+
done
|
|
1718
2321
|
fi
|
|
1719
2322
|
|
|
1720
|
-
|
|
1721
|
-
|
|
2323
|
+
out
|
|
2324
|
+
out "Copied/generated paths:"
|
|
1722
2325
|
if [[ "${#COPIED_PATHS[@]}" -eq 0 ]]; then
|
|
1723
|
-
|
|
2326
|
+
out "- (none)"
|
|
1724
2327
|
else
|
|
1725
|
-
|
|
2328
|
+
local copied_path
|
|
2329
|
+
for copied_path in "${COPIED_PATHS[@]}"; do
|
|
2330
|
+
out "- $copied_path"
|
|
2331
|
+
done
|
|
1726
2332
|
fi
|
|
1727
2333
|
|
|
1728
|
-
|
|
1729
|
-
|
|
2334
|
+
out
|
|
2335
|
+
out "Warnings:"
|
|
1730
2336
|
if [[ "${#WARNINGS[@]}" -eq 0 ]]; then
|
|
1731
|
-
|
|
2337
|
+
out "- (none)"
|
|
2338
|
+
else
|
|
2339
|
+
local warning
|
|
2340
|
+
for warning in "${WARNINGS[@]}"; do
|
|
2341
|
+
out "- $warning"
|
|
2342
|
+
done
|
|
2343
|
+
fi
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
detect_runtime_platform_label() {
|
|
2347
|
+
local platform
|
|
2348
|
+
platform="$(detect_platform)"
|
|
2349
|
+
if [[ "$platform" == "linux" ]] && grep -qiE "(microsoft|wsl)" /proc/version 2>/dev/null; then
|
|
2350
|
+
echo "wsl"
|
|
2351
|
+
return
|
|
2352
|
+
fi
|
|
2353
|
+
echo "$platform"
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
get_agent_binary_name() {
|
|
2357
|
+
local agent_os="$1"
|
|
2358
|
+
case "$agent_os" in
|
|
2359
|
+
codex) echo "codex" ;;
|
|
2360
|
+
claude) echo "claude" ;;
|
|
2361
|
+
opencode) echo "opencode" ;;
|
|
2362
|
+
cursor) echo "cursor-agent" ;;
|
|
2363
|
+
gemini) echo "gemini" ;;
|
|
2364
|
+
antigravity) echo "antigravity" ;;
|
|
2365
|
+
*) echo "" ;;
|
|
2366
|
+
esac
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
print_missing_agent_binary_guides() {
|
|
2370
|
+
local platform_label
|
|
2371
|
+
platform_label="$(detect_runtime_platform_label)"
|
|
2372
|
+
local missing_lines=()
|
|
2373
|
+
local agent_os
|
|
2374
|
+
|
|
2375
|
+
for agent_os in "${SELECTED_AGENT_OS[@]}"; do
|
|
2376
|
+
local binary_name
|
|
2377
|
+
binary_name="$(get_agent_binary_name "$agent_os")"
|
|
2378
|
+
if [[ -z "$binary_name" ]]; then
|
|
2379
|
+
continue
|
|
2380
|
+
fi
|
|
2381
|
+
if command -v "$binary_name" >/dev/null 2>&1; then
|
|
2382
|
+
continue
|
|
2383
|
+
fi
|
|
2384
|
+
|
|
2385
|
+
local install_link=""
|
|
2386
|
+
case "$agent_os" in
|
|
2387
|
+
codex) install_link="https://github.com/openai/codex" ;;
|
|
2388
|
+
claude) install_link="https://docs.anthropic.com/en/docs/claude-code/quickstart" ;;
|
|
2389
|
+
opencode) install_link="https://opencode.ai/docs" ;;
|
|
2390
|
+
cursor) install_link="https://docs.cursor.com/get-started/installation" ;;
|
|
2391
|
+
gemini) install_link="https://github.com/google-gemini/gemini-cli" ;;
|
|
2392
|
+
antigravity) install_link="https://github.com/getantigravity/antigravity" ;;
|
|
2393
|
+
esac
|
|
2394
|
+
|
|
2395
|
+
missing_lines+=("- $agent_os: binary '$binary_name' is not installed on $platform_label.")
|
|
2396
|
+
if [[ -n "$install_link" ]]; then
|
|
2397
|
+
missing_lines+=(" Install guide: $install_link")
|
|
2398
|
+
fi
|
|
2399
|
+
done
|
|
2400
|
+
|
|
2401
|
+
if [[ "${#missing_lines[@]}" -eq 0 ]]; then
|
|
2402
|
+
return
|
|
2403
|
+
fi
|
|
2404
|
+
|
|
2405
|
+
out
|
|
2406
|
+
out "=== Agent binary setup recommendations ===" "$COLOR_HEADER"
|
|
2407
|
+
local missing_line
|
|
2408
|
+
for missing_line in "${missing_lines[@]}"; do
|
|
2409
|
+
out "$missing_line"
|
|
2410
|
+
done
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
changelog_file_path() {
|
|
2414
|
+
local candidate
|
|
2415
|
+
for candidate in "$SCRIPT_DIR/CHANGELOG.md" "$REPO_ROOT/CHANGELOG.md" "$APP_REPO_DIR/CHANGELOG.md"; do
|
|
2416
|
+
[[ -f "$candidate" ]] || continue
|
|
2417
|
+
printf '%s\n' "$candidate"
|
|
2418
|
+
return 0
|
|
2419
|
+
done
|
|
2420
|
+
return 1
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
print_current_changelog() {
|
|
2424
|
+
local changelog version
|
|
2425
|
+
changelog="$(changelog_file_path || true)"
|
|
2426
|
+
version="$(app_version_label)"
|
|
2427
|
+
if [[ -z "$changelog" ]]; then
|
|
2428
|
+
warn "CHANGELOG.md not found; skipping changelog output"
|
|
2429
|
+
return
|
|
2430
|
+
fi
|
|
2431
|
+
|
|
2432
|
+
local section_file
|
|
2433
|
+
section_file="$(mktemp "${TMPDIR:-/tmp}/agentic-changelog.XXXXXX")"
|
|
2434
|
+
awk -v wanted="## $version" '
|
|
2435
|
+
$0 == wanted { in_section = 1; print; next }
|
|
2436
|
+
in_section && /^## / { exit }
|
|
2437
|
+
in_section { print }
|
|
2438
|
+
' "$changelog" > "$section_file"
|
|
2439
|
+
|
|
2440
|
+
if [[ ! -s "$section_file" ]]; then
|
|
2441
|
+
rm -f "$section_file"
|
|
2442
|
+
warn "No changelog section found for $version"
|
|
2443
|
+
return
|
|
2444
|
+
fi
|
|
2445
|
+
|
|
2446
|
+
out
|
|
2447
|
+
out "=== Changelog $version ===" "$COLOR_HEADER"
|
|
2448
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
2449
|
+
if [[ "$line" == "## $version" ]]; then
|
|
2450
|
+
continue
|
|
2451
|
+
fi
|
|
2452
|
+
out "$line"
|
|
2453
|
+
done < "$section_file"
|
|
2454
|
+
rm -f "$section_file"
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
doctor_agent_supported() {
|
|
2458
|
+
case "$1" in
|
|
2459
|
+
codex|opencode|claude|gemini) return 0 ;;
|
|
2460
|
+
*) return 1 ;;
|
|
2461
|
+
esac
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
doctor_enabled() {
|
|
2465
|
+
[[ "$DRY_RUN" != true ]] || return 1
|
|
2466
|
+
[[ "${AGENTIC_DOCTOR:-1}" != "0" ]] || return 1
|
|
2467
|
+
[[ "${AGENTIC_TEST_SOURCE_AGENTIC:-}" != "1" ]] || return 1
|
|
2468
|
+
return 0
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
doctor_prompt() {
|
|
2472
|
+
printf '%s\n' "/develop-feature напиши hello world python"
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
doctor_output_has_fatal_patterns() {
|
|
2476
|
+
local output_file="$1"
|
|
2477
|
+
grep -Eiq 'MCP.*(error|failed|failure|connection|connect|startup)|plugin.*(error|failed|failure)|auth.*(required|failed)|login required|permission.*(denied|required)|SyntaxError|Traceback|Invalid regular expression flags|An unexpected critical error occurred|FatalError|RuntimeError|EPERM|EACCES|panic:' "$output_file"
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
doctor_copy_project() {
|
|
2481
|
+
local dest="$1"
|
|
2482
|
+
mkdir -p "$dest"
|
|
2483
|
+
if [[ -d "$PROJECT_DIR" ]]; then
|
|
2484
|
+
cp -R "$PROJECT_DIR/." "$dest/"
|
|
2485
|
+
fi
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
run_doctor_command() {
|
|
2489
|
+
local agent_os="$1"
|
|
2490
|
+
local work_dir="$2"
|
|
2491
|
+
local output_file="$3"
|
|
2492
|
+
local prompt
|
|
2493
|
+
prompt="$(doctor_prompt)"
|
|
2494
|
+
|
|
2495
|
+
case "$agent_os" in
|
|
2496
|
+
codex)
|
|
2497
|
+
codex exec --skip-git-repo-check --full-auto -C "$work_dir" "$prompt" >"$output_file" 2>&1
|
|
2498
|
+
;;
|
|
2499
|
+
opencode)
|
|
2500
|
+
opencode run --dir "$work_dir" --dangerously-skip-permissions --format json --command develop-feature "напиши hello world python" >"$output_file" 2>&1
|
|
2501
|
+
;;
|
|
2502
|
+
claude)
|
|
2503
|
+
(cd "$work_dir" && claude -p --permission-mode bypassPermissions --output-format stream-json "$prompt") >"$output_file" 2>&1
|
|
2504
|
+
;;
|
|
2505
|
+
gemini)
|
|
2506
|
+
(cd "$work_dir" && gemini --prompt "$prompt") >"$output_file" 2>&1
|
|
2507
|
+
;;
|
|
2508
|
+
*)
|
|
2509
|
+
return 2
|
|
2510
|
+
;;
|
|
2511
|
+
esac
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
run_doctor_for_agent() {
|
|
2515
|
+
local agent_os="$1"
|
|
2516
|
+
local doctor_root="$2"
|
|
2517
|
+
local binary_name
|
|
2518
|
+
binary_name="$(get_agent_binary_name "$agent_os")"
|
|
2519
|
+
|
|
2520
|
+
if [[ -z "$binary_name" ]] || ! command -v "$binary_name" >/dev/null 2>&1; then
|
|
2521
|
+
out "❌ $agent_os: binary '$binary_name' is not installed"
|
|
2522
|
+
return 1
|
|
2523
|
+
fi
|
|
2524
|
+
|
|
2525
|
+
local work_dir output_file status
|
|
2526
|
+
work_dir="$doctor_root/$agent_os"
|
|
2527
|
+
output_file="$doctor_root/$agent_os.log"
|
|
2528
|
+
doctor_copy_project "$work_dir"
|
|
2529
|
+
|
|
2530
|
+
set +e
|
|
2531
|
+
run_doctor_command "$agent_os" "$work_dir" "$output_file"
|
|
2532
|
+
status=$?
|
|
2533
|
+
set -e
|
|
2534
|
+
|
|
2535
|
+
log_file_block "doctor $agent_os" "$output_file"
|
|
2536
|
+
|
|
2537
|
+
if [[ "$status" -ne 0 ]]; then
|
|
2538
|
+
out "❌ $agent_os: /develop-feature smoke failed (exit $status, log: $output_file)"
|
|
2539
|
+
return 1
|
|
2540
|
+
fi
|
|
2541
|
+
|
|
2542
|
+
if doctor_output_has_fatal_patterns "$output_file"; then
|
|
2543
|
+
out "❌ $agent_os: /develop-feature smoke reported integration errors (log: $output_file)"
|
|
2544
|
+
return 1
|
|
2545
|
+
fi
|
|
2546
|
+
|
|
2547
|
+
out "✅ $agent_os: /develop-feature smoke passed"
|
|
2548
|
+
return 0
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
run_agentic_doctor() {
|
|
2552
|
+
if ! doctor_enabled; then
|
|
2553
|
+
log "Agentic doctor skipped"
|
|
2554
|
+
return
|
|
2555
|
+
fi
|
|
2556
|
+
|
|
2557
|
+
local selected_doctor_agents=()
|
|
2558
|
+
local agent_os
|
|
2559
|
+
for agent_os in "${SELECTED_AGENT_OS[@]}"; do
|
|
2560
|
+
if doctor_agent_supported "$agent_os"; then
|
|
2561
|
+
selected_doctor_agents+=("$agent_os")
|
|
2562
|
+
fi
|
|
2563
|
+
done
|
|
2564
|
+
|
|
2565
|
+
if [[ "${#selected_doctor_agents[@]}" -eq 0 ]]; then
|
|
2566
|
+
log "Agentic doctor skipped: no supported real agentos selected"
|
|
2567
|
+
return
|
|
2568
|
+
fi
|
|
2569
|
+
|
|
2570
|
+
local doctor_root
|
|
2571
|
+
doctor_root="$(mktemp -d "${TMPDIR:-/tmp}/agentic-doctor.XXXXXX")"
|
|
2572
|
+
out
|
|
2573
|
+
out "=== Agentic doctor ===" "$COLOR_HEADER"
|
|
2574
|
+
out "Doctor temp root: $doctor_root"
|
|
2575
|
+
|
|
2576
|
+
local failures=0
|
|
2577
|
+
for agent_os in "${selected_doctor_agents[@]}"; do
|
|
2578
|
+
if ! run_doctor_for_agent "$agent_os" "$doctor_root"; then
|
|
2579
|
+
failures=$((failures + 1))
|
|
2580
|
+
fi
|
|
2581
|
+
done
|
|
2582
|
+
|
|
2583
|
+
if [[ "$AGENTIC_DOCTOR_KEEP_TMP" == "1" || "$failures" -gt 0 ]]; then
|
|
2584
|
+
out "Doctor temp root kept: $doctor_root"
|
|
1732
2585
|
else
|
|
1733
|
-
|
|
2586
|
+
rm -rf "$doctor_root"
|
|
2587
|
+
fi
|
|
2588
|
+
|
|
2589
|
+
if [[ "$failures" -gt 0 ]]; then
|
|
2590
|
+
warn "Agentic doctor completed with $failures failing check(s)"
|
|
2591
|
+
else
|
|
2592
|
+
log "Agentic doctor completed successfully"
|
|
1734
2593
|
fi
|
|
1735
2594
|
}
|
|
1736
2595
|
|
|
1737
2596
|
run_install() {
|
|
2597
|
+
init_run_logging
|
|
1738
2598
|
ensure_repo_layout
|
|
1739
|
-
|
|
2599
|
+
ensure_agentic_runtime_requirements
|
|
1740
2600
|
normalize_selected_agent_os
|
|
1741
2601
|
validate_inputs
|
|
1742
2602
|
|
|
@@ -1746,8 +2606,13 @@ run_install() {
|
|
|
1746
2606
|
copy_specialization_assets "$PROJECT_DIR"
|
|
1747
2607
|
generate_agents_md "$PROJECT_DIR"
|
|
1748
2608
|
configure_context7_if_needed
|
|
2609
|
+
configure_mempalace_if_needed
|
|
1749
2610
|
write_agentic_manifest "$PROJECT_DIR"
|
|
1750
2611
|
print_report
|
|
2612
|
+
print_missing_agent_binary_guides
|
|
2613
|
+
print_current_changelog
|
|
2614
|
+
run_agentic_doctor
|
|
2615
|
+
out "Agentic log file: $RUN_LOG_FILE"
|
|
1751
2616
|
}
|
|
1752
2617
|
|
|
1753
2618
|
ascii_banner() {
|
|
@@ -2041,6 +2906,28 @@ choose_single_fzf() {
|
|
|
2041
2906
|
printf '%s\n' "${options[@]}" | fzf "${fzf_args[@]}"
|
|
2042
2907
|
}
|
|
2043
2908
|
|
|
2909
|
+
|
|
2910
|
+
choose_multi_fzf_strict() {
|
|
2911
|
+
local prompt="$1"
|
|
2912
|
+
shift
|
|
2913
|
+
local options=("$@")
|
|
2914
|
+
|
|
2915
|
+
if [[ "${#options[@]}" -eq 0 ]]; then
|
|
2916
|
+
return
|
|
2917
|
+
fi
|
|
2918
|
+
|
|
2919
|
+
local sentinel="<none>"
|
|
2920
|
+
local picked=()
|
|
2921
|
+
readlines picked < <(choose_multi_fzf "$prompt" "$sentinel" "${options[@]}")
|
|
2922
|
+
|
|
2923
|
+
local item
|
|
2924
|
+
for item in "${picked[@]}"; do
|
|
2925
|
+
item="$(trim "$item")"
|
|
2926
|
+
[[ -z "$item" || "$item" == "$sentinel" ]] && continue
|
|
2927
|
+
printf '%s\n' "$item"
|
|
2928
|
+
done
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2044
2931
|
choose_multi_fzf() {
|
|
2045
2932
|
local prompt="$1"
|
|
2046
2933
|
shift
|
|
@@ -2096,11 +2983,13 @@ run_tui() {
|
|
|
2096
2983
|
exit 1
|
|
2097
2984
|
fi
|
|
2098
2985
|
|
|
2986
|
+
ensure_agentic_runtime_requirements
|
|
2987
|
+
|
|
2099
2988
|
pick_theme_if_needed
|
|
2100
2989
|
set_theme_colors
|
|
2101
2990
|
|
|
2102
2991
|
ascii_banner
|
|
2103
|
-
echo "${COLOR_HEADER}$APP_TUI_TITLE${COLOR_RESET}"
|
|
2992
|
+
echo "${COLOR_HEADER}$APP_TUI_TITLE $(app_version_label)${COLOR_RESET}"
|
|
2104
2993
|
echo "${COLOR_DIM}Theme: $THEME (resolved: $ACTIVE_THEME)${COLOR_RESET}"
|
|
2105
2994
|
echo
|
|
2106
2995
|
|
|
@@ -2136,6 +3025,26 @@ run_tui() {
|
|
|
2136
3025
|
SELECTED_AGENT_OS=("${picked_agent_os[@]}")
|
|
2137
3026
|
fi
|
|
2138
3027
|
|
|
3028
|
+
local mcp_options=("context7" "mempalace")
|
|
3029
|
+
local picked_mcps=()
|
|
3030
|
+
if [[ "$use_fzf" == true ]]; then
|
|
3031
|
+
readlines picked_mcps < <(choose_multi_fzf_strict "Select optional MCP integration(s):" "${mcp_options[@]}")
|
|
3032
|
+
else
|
|
3033
|
+
local picked_mcps_output
|
|
3034
|
+
picked_mcps_output="$(choose_multi_by_index "Select optional MCP integration(s):" "<none>" "${mcp_options[@]}")"
|
|
3035
|
+
readlines picked_mcps <<< "$picked_mcps_output"
|
|
3036
|
+
fi
|
|
3037
|
+
|
|
3038
|
+
AGENTIC_ENABLE_CONTEXT7="n"
|
|
3039
|
+
AGENTIC_ENABLE_MEMPALACE="n"
|
|
3040
|
+
local picked_mcp
|
|
3041
|
+
for picked_mcp in "${picked_mcps[@]}"; do
|
|
3042
|
+
case "$picked_mcp" in
|
|
3043
|
+
context7) AGENTIC_ENABLE_CONTEXT7="y" ;;
|
|
3044
|
+
mempalace) AGENTIC_ENABLE_MEMPALACE="y" ;;
|
|
3045
|
+
esac
|
|
3046
|
+
done
|
|
3047
|
+
|
|
2139
3048
|
local areas=()
|
|
2140
3049
|
readlines areas < <(list_areas)
|
|
2141
3050
|
|
|
@@ -2274,8 +3183,13 @@ update_installed_binary_from_repo() {
|
|
|
2274
3183
|
return
|
|
2275
3184
|
fi
|
|
2276
3185
|
|
|
2277
|
-
|
|
2278
|
-
|
|
3186
|
+
local target_dir target_tmp
|
|
3187
|
+
target_dir="$(dirname "$target")"
|
|
3188
|
+
target_tmp="$(mktemp "$target_dir/.agentic-update.XXXXXX")"
|
|
3189
|
+
|
|
3190
|
+
cp "$source_path" "$target_tmp"
|
|
3191
|
+
chmod +x "$target_tmp"
|
|
3192
|
+
mv -f "$target_tmp" "$target"
|
|
2279
3193
|
log "Updated installed binary: $target"
|
|
2280
3194
|
}
|
|
2281
3195
|
|
|
@@ -2396,6 +3310,9 @@ case "$COMMAND" in
|
|
|
2396
3310
|
shift 2
|
|
2397
3311
|
;;
|
|
2398
3312
|
--agent-os)
|
|
3313
|
+
if [[ "${#SELECTED_AGENT_OS[@]}" -eq 1 && "${SELECTED_AGENT_OS[0]}" == "$DEFAULT_AGENT_OS" ]]; then
|
|
3314
|
+
SELECTED_AGENT_OS=()
|
|
3315
|
+
fi
|
|
2399
3316
|
split_csv "$2" SELECTED_AGENT_OS
|
|
2400
3317
|
shift 2
|
|
2401
3318
|
;;
|
|
@@ -2419,6 +3336,10 @@ case "$COMMAND" in
|
|
|
2419
3336
|
DRY_RUN=true
|
|
2420
3337
|
shift
|
|
2421
3338
|
;;
|
|
3339
|
+
--no-doctor)
|
|
3340
|
+
AGENTIC_DOCTOR=0
|
|
3341
|
+
shift
|
|
3342
|
+
;;
|
|
2422
3343
|
-h|--help)
|
|
2423
3344
|
usage
|
|
2424
3345
|
exit 0
|
|
@@ -2460,6 +3381,10 @@ case "$COMMAND" in
|
|
|
2460
3381
|
DRY_RUN=true
|
|
2461
3382
|
shift
|
|
2462
3383
|
;;
|
|
3384
|
+
--no-doctor)
|
|
3385
|
+
AGENTIC_DOCTOR=0
|
|
3386
|
+
shift
|
|
3387
|
+
;;
|
|
2463
3388
|
-h|--help)
|
|
2464
3389
|
usage
|
|
2465
3390
|
exit 0
|
|
@@ -2546,6 +3471,10 @@ case "$COMMAND" in
|
|
|
2546
3471
|
usage
|
|
2547
3472
|
;;
|
|
2548
3473
|
|
|
3474
|
+
-V|--version|version)
|
|
3475
|
+
app_version_label
|
|
3476
|
+
;;
|
|
3477
|
+
|
|
2549
3478
|
*)
|
|
2550
3479
|
usage
|
|
2551
3480
|
exit 1
|