aid-installer 1.1.1 → 2.0.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/README.md +2 -2
- package/VERSION +1 -1
- package/bin/aid +158 -20
- package/bin/aid.ps1 +189 -41
- package/dashboard/home.html +41 -4
- package/dashboard/reader/derivation.py +228 -5
- package/dashboard/reader/models.py +22 -0
- package/dashboard/reader/parsers.py +127 -0
- package/dashboard/reader/reader.py +6 -1
- package/dashboard/server/reader.mjs +313 -0
- package/dashboard/server/server.py +12 -0
- package/lib/AidInstallCore.psm1 +439 -63
- package/lib/aid-install-core.sh +384 -36
- package/package.json +9 -6
- package/scripts/vendor.js +0 -98
package/lib/aid-install-core.sh
CHANGED
|
@@ -81,6 +81,19 @@ _root_agent_file() {
|
|
|
81
81
|
esac
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
# _root_dir <tool> - print the tool's install-tree root dir name (relative to the
|
|
85
|
+
# project root). The AID-own subtree lives at <root>/aid/ (e.g. .claude/aid/).
|
|
86
|
+
# Mirrors the per-tool dispatch in install_tool.
|
|
87
|
+
_root_dir() {
|
|
88
|
+
case "$1" in
|
|
89
|
+
claude-code) echo ".claude" ;;
|
|
90
|
+
codex) echo ".codex" ;;
|
|
91
|
+
cursor) echo ".cursor" ;;
|
|
92
|
+
copilot-cli) echo ".github" ;;
|
|
93
|
+
antigravity) echo ".agent" ;;
|
|
94
|
+
esac
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
# ---------------------------------------------------------------------------
|
|
85
98
|
# Utility
|
|
86
99
|
# ---------------------------------------------------------------------------
|
|
@@ -315,7 +328,11 @@ copy_file() {
|
|
|
315
328
|
mkdir -p "$dst_dir"
|
|
316
329
|
|
|
317
330
|
if [[ ! -e "$dst" ]]; then
|
|
318
|
-
cp "$src" "$dst"
|
|
331
|
+
if ! cp "$src" "$dst"; then
|
|
332
|
+
echo "ERROR: aid-install-core: copy failed: ${dst}" >&2
|
|
333
|
+
_COPY_COUNT_FAILED=$((_COPY_COUNT_FAILED + 1))
|
|
334
|
+
return 0
|
|
335
|
+
fi
|
|
319
336
|
_COPY_COUNT_COPIED=$((_COPY_COUNT_COPIED + 1))
|
|
320
337
|
[[ "${AID_VERBOSE:-0}" -eq 1 ]] && echo "Copied: ${dst}"
|
|
321
338
|
return 0
|
|
@@ -327,15 +344,21 @@ copy_file() {
|
|
|
327
344
|
return 0
|
|
328
345
|
fi
|
|
329
346
|
|
|
330
|
-
# File exists and differs.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
347
|
+
# File exists and differs -> always overwrite (work-007).
|
|
348
|
+
# AID-owned files track the bundle, which is the source of truth, so an
|
|
349
|
+
# add/update must bring them current. `force` is retained in the signature
|
|
350
|
+
# for back-compat and bash<->PS parity but no longer gates this overwrite:
|
|
351
|
+
# skip-on-diff silently left stale files behind on in-place upgrades (e.g. an
|
|
352
|
+
# old flat-path skill surviving next to relocated .aid/ scripts). User-owned
|
|
353
|
+
# root agent files (CLAUDE.md/AGENTS.md) never reach copy_file -- they are
|
|
354
|
+
# handled by _copy_root_agent_file, which keeps its own force gating.
|
|
355
|
+
if ! cp "$src" "$dst"; then
|
|
356
|
+
echo "ERROR: aid-install-core: copy failed: ${dst}" >&2
|
|
357
|
+
_COPY_COUNT_FAILED=$((_COPY_COUNT_FAILED + 1))
|
|
358
|
+
return 0
|
|
338
359
|
fi
|
|
360
|
+
_COPY_COUNT_UPDATED=$((_COPY_COUNT_UPDATED + 1))
|
|
361
|
+
[[ "${AID_VERBOSE:-0}" -eq 1 ]] && echo "Updated: ${dst}"
|
|
339
362
|
}
|
|
340
363
|
|
|
341
364
|
# copy_dir <src_dir> <dst_dir> [force]
|
|
@@ -428,7 +451,11 @@ _copy_root_agent_file() {
|
|
|
428
451
|
# then lines after the closing marker.
|
|
429
452
|
local tmp
|
|
430
453
|
tmp="$(mktemp "${dst_dir}/.aid-root-agent.XXXXXX")"
|
|
431
|
-
awk -v
|
|
454
|
+
# awk -v applies C-escape processing to the value; double every backslash
|
|
455
|
+
# so a region containing "\" (or "\n"-like text) passes through literally
|
|
456
|
+
# (work-007 C7). No-op for the current backslash-free region.
|
|
457
|
+
local src_region_esc="${src_region//\\/\\\\}"
|
|
458
|
+
awk -v region="$src_region_esc" '
|
|
432
459
|
BEGIN { in_aid=0; printed_region=0 }
|
|
433
460
|
/^<!-- AID:BEGIN -->/ {
|
|
434
461
|
if (!printed_region) {
|
|
@@ -471,11 +498,13 @@ _copy_root_agent_file() {
|
|
|
471
498
|
# stem match and re-insert as a marked region.
|
|
472
499
|
#
|
|
473
500
|
# AID section stems to excise (exact heading stem; tolerate trailing
|
|
474
|
-
# parenthetical like " (global)" or " (IMPERATIVE)")
|
|
501
|
+
# parenthetical like " (global)" or " (IMPERATIVE)") -- MUST match every
|
|
502
|
+
# "## " heading in the region (see is_aid_heading):
|
|
503
|
+
# ## Tracking discipline
|
|
475
504
|
# ## Knowledge Base
|
|
505
|
+
# ## Workflow
|
|
476
506
|
# ## Review output format
|
|
477
507
|
# ## Permissions
|
|
478
|
-
# ## Tracking discipline
|
|
479
508
|
#
|
|
480
509
|
# A section runs from its "## Stem..." heading line until the next "## "
|
|
481
510
|
# heading (exclusive) or end-of-file.
|
|
@@ -487,27 +516,34 @@ _copy_root_agent_file() {
|
|
|
487
516
|
# Extract the new marked region from source.
|
|
488
517
|
local new_region
|
|
489
518
|
new_region="$(awk '/^<!-- AID:BEGIN -->/{found=1} found{print} /^<!-- AID:END -->/{if(found){exit}}' "$src")"
|
|
519
|
+
# awk -v applies C-escape processing; double backslashes so the region passes
|
|
520
|
+
# through literally (work-007 C7). No-op for the current backslash-free region.
|
|
521
|
+
local new_region_esc="${new_region//\\/\\\\}"
|
|
490
522
|
|
|
491
523
|
local tmp
|
|
492
524
|
tmp="$(mktemp "${dst_dir}/.aid-root-agent.XXXXXX")"
|
|
493
525
|
|
|
494
|
-
# Use awk to perform the excise-and-reinsert in one pass
|
|
526
|
+
# Use awk to perform the excise-and-reinsert in one pass (region passed
|
|
527
|
+
# backslash-doubled via new_region_esc -- see C7 note above).
|
|
495
528
|
# The awk script:
|
|
496
529
|
# - Identifies AID-managed section headings by stem prefix match.
|
|
497
530
|
# - Suppresses those sections (and their body lines).
|
|
498
531
|
# - At the position of the first suppressed section, emits the new region.
|
|
499
532
|
# - All other lines are emitted verbatim.
|
|
500
|
-
awk -v new_region="$
|
|
533
|
+
awk -v new_region="$new_region_esc" '
|
|
501
534
|
function is_aid_heading(line, stem) {
|
|
502
535
|
# Match "## StemText" optionally followed by " (anything)".
|
|
503
|
-
# Stems
|
|
504
|
-
#
|
|
536
|
+
# Stems must cover EVERY "## " heading inside the shipped AID:BEGIN/END
|
|
537
|
+
# region (Tracking discipline, Knowledge Base, Workflow, Review output
|
|
538
|
+
# format, Permissions) -- a stem missing here causes a duplicate section
|
|
539
|
+
# on the C2 (no-marker) migration path (work-007: Workflow was omitted).
|
|
505
540
|
if (line !~ /^## /) return 0
|
|
506
541
|
stem = line
|
|
507
542
|
gsub(/^## /, "", stem)
|
|
508
543
|
# Strip trailing parenthetical suffix: " (..." -> ""
|
|
509
544
|
gsub(/ \([^)]*\)$/, "", stem)
|
|
510
545
|
if (stem == "Knowledge Base") return 1
|
|
546
|
+
if (stem == "Workflow") return 1
|
|
511
547
|
if (stem == "Review output format") return 1
|
|
512
548
|
if (stem == "Permissions") return 1
|
|
513
549
|
if (stem == "Tracking discipline") return 1
|
|
@@ -1695,6 +1731,8 @@ _prune_tool_dirs() {
|
|
|
1695
1731
|
|
|
1696
1732
|
# ------------------------------------------------------------------
|
|
1697
1733
|
# Per-tool scoping: which native dirs + which aid/ root to walk.
|
|
1734
|
+
# New layout (work-005/delivery-001): codex is unified under .codex/;
|
|
1735
|
+
# cursor and antigravity no longer ship rules/ dirs.
|
|
1698
1736
|
# ------------------------------------------------------------------
|
|
1699
1737
|
case "$tool" in
|
|
1700
1738
|
claude-code)
|
|
@@ -1703,15 +1741,15 @@ _prune_tool_dirs() {
|
|
|
1703
1741
|
_prune_aid_subtree "${target}/.claude/aid"
|
|
1704
1742
|
;;
|
|
1705
1743
|
codex)
|
|
1706
|
-
#
|
|
1744
|
+
# New unified layout: everything under .codex/ (agents, skills, aid).
|
|
1707
1745
|
_prune_native_dir "${target}/.codex/agents"
|
|
1708
|
-
_prune_native_dir "${target}/.
|
|
1709
|
-
_prune_aid_subtree "${target}/.
|
|
1746
|
+
_prune_native_dir "${target}/.codex/skills"
|
|
1747
|
+
_prune_aid_subtree "${target}/.codex/aid"
|
|
1710
1748
|
;;
|
|
1711
1749
|
cursor)
|
|
1750
|
+
# rules/ dir removed from new layout; agents/skills/aid remain.
|
|
1712
1751
|
_prune_native_dir "${target}/.cursor/agents"
|
|
1713
1752
|
_prune_native_dir "${target}/.cursor/skills"
|
|
1714
|
-
_prune_native_dir "${target}/.cursor/rules"
|
|
1715
1753
|
_prune_aid_subtree "${target}/.cursor/aid"
|
|
1716
1754
|
;;
|
|
1717
1755
|
copilot-cli)
|
|
@@ -1721,7 +1759,8 @@ _prune_tool_dirs() {
|
|
|
1721
1759
|
_prune_aid_subtree "${target}/.github/aid"
|
|
1722
1760
|
;;
|
|
1723
1761
|
antigravity)
|
|
1724
|
-
|
|
1762
|
+
# rules/ dir removed from new layout; agents/skills/aid remain.
|
|
1763
|
+
_prune_native_dir "${target}/.agent/agents"
|
|
1725
1764
|
_prune_native_dir "${target}/.agent/skills"
|
|
1726
1765
|
_prune_aid_subtree "${target}/.agent/aid"
|
|
1727
1766
|
;;
|
|
@@ -1735,6 +1774,138 @@ _prune_tool_dirs() {
|
|
|
1735
1774
|
unset -f _in_set _prune_native_dir _prune_aid_subtree
|
|
1736
1775
|
}
|
|
1737
1776
|
|
|
1777
|
+
# ---------------------------------------------------------------------------
|
|
1778
|
+
# Retired-root migration sweep (FR7/FR7a)
|
|
1779
|
+
# ---------------------------------------------------------------------------
|
|
1780
|
+
|
|
1781
|
+
# _migrate_retired_layout <target> <tool>
|
|
1782
|
+
#
|
|
1783
|
+
# Complete-replacement migration: removes AID-owned content from the static
|
|
1784
|
+
# list of retired AID roots that no longer exist in the new bundle layout.
|
|
1785
|
+
# Called from install_tool BEFORE _prune_tool_dirs (same aid update pass).
|
|
1786
|
+
#
|
|
1787
|
+
# Retired roots swept per tool:
|
|
1788
|
+
# codex: .agents/ (split layout retired)
|
|
1789
|
+
# cursor: .cursor/rules/ (rules dir retired)
|
|
1790
|
+
# antigravity: .agent/rules/ (rules dir retired)
|
|
1791
|
+
# all tools: .cursor/rules/, .agent/rules/ are cursor/antigravity only
|
|
1792
|
+
#
|
|
1793
|
+
# Ownership markers applied (content-isolation.md rules 1+2):
|
|
1794
|
+
# Marker 1: filename starts with "aid-" (tool-native dir files)
|
|
1795
|
+
# Marker 2: lives inside an "aid/" subtree
|
|
1796
|
+
#
|
|
1797
|
+
# Marker 3 (AID:BEGIN/END region in root files) is NOT touched here;
|
|
1798
|
+
# that is handled by _copy_root_agent_file exclusively.
|
|
1799
|
+
#
|
|
1800
|
+
# User content (no marker) is NEVER removed.
|
|
1801
|
+
# Idempotent: a no-op when the retired path is already absent.
|
|
1802
|
+
# Returns count of retired items removed (written to _MIGRATE_RETIRED_COUNT).
|
|
1803
|
+
_migrate_retired_layout() {
|
|
1804
|
+
local target="$1"
|
|
1805
|
+
local tool="$2"
|
|
1806
|
+
local list_only="${3:-0}" # 1 = dry-run enumeration only (no removals)
|
|
1807
|
+
_MIGRATE_RETIRED_COUNT=0
|
|
1808
|
+
|
|
1809
|
+
# Return 1 if a file is AID-owned (marker 1 or 2); 0 if user content.
|
|
1810
|
+
_is_aid_owned_file() {
|
|
1811
|
+
local f="$1"
|
|
1812
|
+
local base
|
|
1813
|
+
base="$(basename "$f")"
|
|
1814
|
+
# Marker 1: aid- prefix.
|
|
1815
|
+
if [[ "$base" == aid-* ]]; then return 0; fi
|
|
1816
|
+
# Marker 2: lives inside an aid/ folder (any path component named "aid").
|
|
1817
|
+
local dir
|
|
1818
|
+
dir="$(dirname "$f")"
|
|
1819
|
+
while [[ "$dir" != "$target" && "$dir" != "/" && "$dir" != "." ]]; do
|
|
1820
|
+
if [[ "$(basename "$dir")" == "aid" ]]; then return 0; fi
|
|
1821
|
+
dir="$(dirname "$dir")"
|
|
1822
|
+
done
|
|
1823
|
+
# No marker -- user content.
|
|
1824
|
+
return 1
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
# Move one AID-owned file (marker 1 or 2) to <target>/.aid/.trash/<rel-path>.
|
|
1828
|
+
# In list_only mode: print the path instead of moving it (no writes).
|
|
1829
|
+
_retire_file() {
|
|
1830
|
+
local f="$1"
|
|
1831
|
+
[[ -f "$f" ]] || return 0
|
|
1832
|
+
if _is_aid_owned_file "$f"; then
|
|
1833
|
+
if [[ "$list_only" -eq 1 ]]; then
|
|
1834
|
+
echo " move to trash: ${f}"
|
|
1835
|
+
_MIGRATE_RETIRED_COUNT=$((_MIGRATE_RETIRED_COUNT + 1))
|
|
1836
|
+
else
|
|
1837
|
+
# Compute path relative to $target and move to the trash dir.
|
|
1838
|
+
local rel dest
|
|
1839
|
+
rel="${f#${target}/}"
|
|
1840
|
+
dest="${target}/.aid/.trash/${rel}"
|
|
1841
|
+
mkdir -p "$(dirname "$dest")"
|
|
1842
|
+
mv -f "$f" "$dest"
|
|
1843
|
+
_MIGRATE_RETIRED_COUNT=$((_MIGRATE_RETIRED_COUNT + 1))
|
|
1844
|
+
if [[ "${AID_VERBOSE:-0}" -eq 1 ]]; then echo "Trashed: ${f} -> ${dest}"; fi
|
|
1845
|
+
fi
|
|
1846
|
+
fi
|
|
1847
|
+
return 0
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
# Sweep one retired root directory: remove AID-owned files, then prune
|
|
1851
|
+
# now-empty subdirs, then remove the root dir itself if now empty.
|
|
1852
|
+
# In list_only mode: enumerate would-be-removed files, make no changes.
|
|
1853
|
+
_sweep_retired_root() {
|
|
1854
|
+
local rdir="$1"
|
|
1855
|
+
[[ -d "$rdir" ]] || return 0
|
|
1856
|
+
# Walk all files under the retired root.
|
|
1857
|
+
local fpath
|
|
1858
|
+
while IFS= read -r -d '' fpath; do
|
|
1859
|
+
_retire_file "$fpath"
|
|
1860
|
+
done < <(find "$rdir" -type f -print0 2>/dev/null | sort -z)
|
|
1861
|
+
if [[ "$list_only" -eq 1 ]]; then return 0; fi
|
|
1862
|
+
# Prune now-empty subdirs (deepest first).
|
|
1863
|
+
local dpath
|
|
1864
|
+
while IFS= read -r dpath; do
|
|
1865
|
+
if [[ -d "$dpath" ]]; then
|
|
1866
|
+
local rem
|
|
1867
|
+
rem="$(find "$dpath" -mindepth 1 2>/dev/null | head -1)"
|
|
1868
|
+
if [[ -z "$rem" ]]; then
|
|
1869
|
+
rmdir "$dpath" 2>/dev/null || true
|
|
1870
|
+
if [[ "${AID_VERBOSE:-0}" -eq 1 ]]; then echo "Retired dir: ${dpath}"; fi
|
|
1871
|
+
fi
|
|
1872
|
+
fi
|
|
1873
|
+
done < <(find "$rdir" -mindepth 1 -type d 2>/dev/null | sort -r)
|
|
1874
|
+
# Remove the retired root itself if now empty.
|
|
1875
|
+
if [[ -d "$rdir" ]]; then
|
|
1876
|
+
local rem
|
|
1877
|
+
rem="$(find "$rdir" -mindepth 1 2>/dev/null | head -1)"
|
|
1878
|
+
if [[ -z "$rem" ]]; then
|
|
1879
|
+
rmdir "$rdir" 2>/dev/null || true
|
|
1880
|
+
if [[ "${AID_VERBOSE:-0}" -eq 1 ]]; then echo "Retired root dir: ${rdir}"; fi
|
|
1881
|
+
fi
|
|
1882
|
+
fi
|
|
1883
|
+
return 0
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
case "$tool" in
|
|
1887
|
+
codex)
|
|
1888
|
+
# Retired root: .agents/ (old split layout -- skills/ + aid/ lived here).
|
|
1889
|
+
_sweep_retired_root "${target}/.agents"
|
|
1890
|
+
;;
|
|
1891
|
+
cursor)
|
|
1892
|
+
# Retired root: .cursor/rules/ (rules dir no longer in new layout).
|
|
1893
|
+
_sweep_retired_root "${target}/.cursor/rules"
|
|
1894
|
+
;;
|
|
1895
|
+
antigravity)
|
|
1896
|
+
# Retired root: .agent/rules/ (rules dir no longer in new layout).
|
|
1897
|
+
_sweep_retired_root "${target}/.agent/rules"
|
|
1898
|
+
;;
|
|
1899
|
+
# claude-code and copilot-cli have no retired roots in this migration.
|
|
1900
|
+
esac
|
|
1901
|
+
|
|
1902
|
+
if [[ "$list_only" -eq 0 && "$_MIGRATE_RETIRED_COUNT" -gt 0 ]]; then
|
|
1903
|
+
echo " ${_MIGRATE_RETIRED_COUNT} retired AID file(s) moved to .aid/.trash/"
|
|
1904
|
+
fi
|
|
1905
|
+
|
|
1906
|
+
unset -f _is_aid_owned_file _retire_file _sweep_retired_root
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1738
1909
|
# ---------------------------------------------------------------------------
|
|
1739
1910
|
# Version marker
|
|
1740
1911
|
# ---------------------------------------------------------------------------
|
|
@@ -1746,6 +1917,133 @@ write_version_marker() {
|
|
|
1746
1917
|
printf '%s\n' "$version" > "${target}/.aid/.aid-version"
|
|
1747
1918
|
}
|
|
1748
1919
|
|
|
1920
|
+
# ---------------------------------------------------------------------------
|
|
1921
|
+
# Project-level provisioning (work-007): required runtime file + VCS hygiene.
|
|
1922
|
+
# Both are idempotent and safe to run once per tool during a multi-tool add.
|
|
1923
|
+
# ---------------------------------------------------------------------------
|
|
1924
|
+
|
|
1925
|
+
# seed_settings_yml <target> <tool>
|
|
1926
|
+
# Seed <target>/.aid/settings.yml from the tool's just-installed template when it
|
|
1927
|
+
# does not already exist. NEVER overwrites an existing settings.yml -- it holds
|
|
1928
|
+
# user config (project identity, grades). Not recorded in the manifest: it is
|
|
1929
|
+
# user data and must survive `aid remove`/prune and never flow through the copy
|
|
1930
|
+
# engine. Sets _CORE_SEEDED_SETTINGS=1 when a file was created, else 0.
|
|
1931
|
+
seed_settings_yml() {
|
|
1932
|
+
local target="$1" tool="$2"
|
|
1933
|
+
local root dst tmpl
|
|
1934
|
+
_CORE_SEEDED_SETTINGS=0
|
|
1935
|
+
root="$(_root_dir "$tool")"
|
|
1936
|
+
[[ -z "$root" ]] && return 0
|
|
1937
|
+
dst="${target}/.aid/settings.yml"
|
|
1938
|
+
tmpl="${target}/${root}/aid/templates/settings.yml"
|
|
1939
|
+
[[ -f "$dst" ]] && return 0 # never clobber user config
|
|
1940
|
+
# Template absent -> a tool bundle shipped without aid/templates/settings.yml.
|
|
1941
|
+
# Surface it (work-007 C5): a silent skip re-triggers the "no settings.yml"
|
|
1942
|
+
# class this seed exists to fix, and the format gate would then warn forever.
|
|
1943
|
+
if [[ ! -f "$tmpl" ]]; then
|
|
1944
|
+
echo "WARN: aid-install-core: settings template missing (${tmpl}); .aid/settings.yml not seeded for '${tool}'." >&2
|
|
1945
|
+
return 0
|
|
1946
|
+
fi
|
|
1947
|
+
mkdir -p "${target}/.aid"
|
|
1948
|
+
# Seed from the template, but STAMP the format_version as the first line so
|
|
1949
|
+
# the settings-format gate does not warn on every subsequent command (the raw
|
|
1950
|
+
# template ships unstamped; era-b synthesis stamps -- we match it here). The
|
|
1951
|
+
# value mirrors bin/aid's AID_SUPPORTED_FORMAT (fallback 1 for install.sh).
|
|
1952
|
+
local _fmt _seed_tmp
|
|
1953
|
+
_fmt="${AID_SUPPORTED_FORMAT:-1}"
|
|
1954
|
+
_seed_tmp="$(mktemp "${dst}.aid-tmp.XXXXXX")" || {
|
|
1955
|
+
echo "WARN: aid-install-core: could not create temp file to seed .aid/settings.yml for '${tool}'." >&2
|
|
1956
|
+
return 0
|
|
1957
|
+
}
|
|
1958
|
+
if grep -q '^format_version:' "$tmpl" 2>/dev/null; then
|
|
1959
|
+
cp "$tmpl" "$_seed_tmp"
|
|
1960
|
+
else
|
|
1961
|
+
{ printf 'format_version: %s\n' "$_fmt"; cat "$tmpl"; } > "$_seed_tmp"
|
|
1962
|
+
fi
|
|
1963
|
+
if ! mv -f "$_seed_tmp" "$dst"; then
|
|
1964
|
+
rm -f "$_seed_tmp" 2>/dev/null
|
|
1965
|
+
echo "WARN: aid-install-core: could not write .aid/settings.yml for '${tool}'." >&2
|
|
1966
|
+
return 0
|
|
1967
|
+
fi
|
|
1968
|
+
_CORE_SEEDED_SETTINGS=1
|
|
1969
|
+
return 0
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
# AID-managed .gitignore region markers. The lines BETWEEN the markers are owned
|
|
1973
|
+
# by the installer and rewritten on every add/update; everything OUTSIDE the
|
|
1974
|
+
# markers is preserved verbatim (mirrors the root-agent region-update pattern).
|
|
1975
|
+
_AID_GI_BEGIN="# >>> AID managed -- do not edit (aid add/update maintains this block) >>>"
|
|
1976
|
+
_AID_GI_END="# <<< AID managed <<<"
|
|
1977
|
+
|
|
1978
|
+
# _aid_gitignore_block - print the current AID-managed .gitignore block (markers
|
|
1979
|
+
# inclusive). The exclusion set is the single source of truth for transient
|
|
1980
|
+
# .aid/ artifacts that must never be committed.
|
|
1981
|
+
_aid_gitignore_block() {
|
|
1982
|
+
printf '%s\n' "$_AID_GI_BEGIN"
|
|
1983
|
+
printf '%s\n' \
|
|
1984
|
+
".aid/.temp/" \
|
|
1985
|
+
".aid/.trash/" \
|
|
1986
|
+
".aid/.heartbeat/" \
|
|
1987
|
+
".aid/generated/" \
|
|
1988
|
+
".aid/knowledge/.cache/" \
|
|
1989
|
+
".aid/knowledge/.manual-checklist.json" \
|
|
1990
|
+
".aid/knowledge/.spot-check-facts.txt"
|
|
1991
|
+
printf '%s' "$_AID_GI_END"
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
# update_gitignore <target>
|
|
1995
|
+
# Ensure <target>/.gitignore carries the AID-managed region with the current
|
|
1996
|
+
# transient .aid/ exclusions. Creates the file if absent; otherwise strips any
|
|
1997
|
+
# existing AID region, preserves all user content, and appends a fresh region
|
|
1998
|
+
# (with a single blank-line separator). Idempotent: a second run produces a
|
|
1999
|
+
# byte-identical file. Sets _CORE_GITIGNORE_ACTION to created|updated|unchanged.
|
|
2000
|
+
update_gitignore() {
|
|
2001
|
+
local target="$1"
|
|
2002
|
+
local gi="${target}/.gitignore"
|
|
2003
|
+
local block tmp
|
|
2004
|
+
_CORE_GITIGNORE_ACTION="unchanged"
|
|
2005
|
+
block="$(_aid_gitignore_block)"
|
|
2006
|
+
|
|
2007
|
+
if [[ ! -f "$gi" ]]; then
|
|
2008
|
+
printf '%s\n' "$block" > "$gi"
|
|
2009
|
+
_CORE_GITIGNORE_ACTION="created"
|
|
2010
|
+
return 0
|
|
2011
|
+
fi
|
|
2012
|
+
|
|
2013
|
+
# Normalize the existing file to LF for BOTH processing and comparison, so a
|
|
2014
|
+
# line-ending-only difference (a CRLF .gitignore) does NOT force a rewrite
|
|
2015
|
+
# every run. Parity with PS Update-AidGitignore, which compares CRLF->LF-
|
|
2016
|
+
# normalized content (work-007 C3). If unchanged, the original file is left
|
|
2017
|
+
# byte-untouched (CRLF preserved); only a real region change triggers a write.
|
|
2018
|
+
local norm
|
|
2019
|
+
norm="$(mktemp "${target}/.gitignore.norm.XXXXXX")"
|
|
2020
|
+
tr -d '\r' < "$gi" > "$norm"
|
|
2021
|
+
|
|
2022
|
+
tmp="$(mktemp "${target}/.gitignore.aid.XXXXXX")"
|
|
2023
|
+
awk -v b="$_AID_GI_BEGIN" -v e="$_AID_GI_END" -v blk="$block" '
|
|
2024
|
+
$0==b {inblk=1; next}
|
|
2025
|
+
inblk && $0==e {inblk=0; next}
|
|
2026
|
+
inblk {next}
|
|
2027
|
+
{lines[n++]=$0}
|
|
2028
|
+
END {
|
|
2029
|
+
while (n>0 && lines[n-1] ~ /^[ \t]*$/) n--
|
|
2030
|
+
for (i=0;i<n;i++) print lines[i]
|
|
2031
|
+
if (n>0) print ""
|
|
2032
|
+
print blk
|
|
2033
|
+
}
|
|
2034
|
+
' "$norm" > "$tmp"
|
|
2035
|
+
|
|
2036
|
+
if cmp -s "$tmp" "$norm"; then
|
|
2037
|
+
rm -f "$tmp" "$norm"
|
|
2038
|
+
_CORE_GITIGNORE_ACTION="unchanged"
|
|
2039
|
+
else
|
|
2040
|
+
rm -f "$norm"
|
|
2041
|
+
mv -f "$tmp" "$gi"
|
|
2042
|
+
_CORE_GITIGNORE_ACTION="updated"
|
|
2043
|
+
fi
|
|
2044
|
+
return 0
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1749
2047
|
# ---------------------------------------------------------------------------
|
|
1750
2048
|
# High-level install_tool
|
|
1751
2049
|
# ---------------------------------------------------------------------------
|
|
@@ -1768,6 +2066,7 @@ install_tool() {
|
|
|
1768
2066
|
_COPY_COUNT_UPTODATE=0
|
|
1769
2067
|
_COPY_COUNT_UPDATED=0
|
|
1770
2068
|
_COPY_COUNT_SKIPPED=0
|
|
2069
|
+
_COPY_COUNT_FAILED=0
|
|
1771
2070
|
|
|
1772
2071
|
local root_agent
|
|
1773
2072
|
root_agent="$(_root_agent_file "$tool")"
|
|
@@ -1786,7 +2085,8 @@ install_tool() {
|
|
|
1786
2085
|
fi
|
|
1787
2086
|
;;
|
|
1788
2087
|
codex)
|
|
1789
|
-
# .codex/
|
|
2088
|
+
# New unified layout: .codex/ only (agents, skills, aid all under .codex/).
|
|
2089
|
+
# .agents/ is the RETIRED split layout -- handled by _migrate_retired_layout.
|
|
1790
2090
|
if [[ -d "${staging}/.codex" ]]; then
|
|
1791
2091
|
copy_dir "${staging}/.codex" "${target}/.codex" "$force"
|
|
1792
2092
|
while IFS= read -r -d '' f; do
|
|
@@ -1794,13 +2094,6 @@ install_tool() {
|
|
|
1794
2094
|
install_paths+=("$rel")
|
|
1795
2095
|
done < <(find "${staging}/.codex" -type f -print0 2>/dev/null | sort -z)
|
|
1796
2096
|
fi
|
|
1797
|
-
if [[ -d "${staging}/.agents" ]]; then
|
|
1798
|
-
copy_dir "${staging}/.agents" "${target}/.agents" "$force"
|
|
1799
|
-
while IFS= read -r -d '' f; do
|
|
1800
|
-
local rel="${f#${staging}/}"
|
|
1801
|
-
install_paths+=("$rel")
|
|
1802
|
-
done < <(find "${staging}/.agents" -type f -print0 2>/dev/null | sort -z)
|
|
1803
|
-
fi
|
|
1804
2097
|
;;
|
|
1805
2098
|
cursor)
|
|
1806
2099
|
# .cursor/ + AGENTS.md
|
|
@@ -1834,6 +2127,13 @@ install_tool() {
|
|
|
1834
2127
|
;;
|
|
1835
2128
|
esac
|
|
1836
2129
|
|
|
2130
|
+
# Abort loudly if any AID-owned file failed to copy -- do NOT proceed to
|
|
2131
|
+
# write a manifest that records a partial install as success (work-007 C4).
|
|
2132
|
+
if [[ "${_COPY_COUNT_FAILED:-0}" -gt 0 ]]; then
|
|
2133
|
+
echo "ERROR: aid-install-core: ${_COPY_COUNT_FAILED} file(s) failed to copy for '${tool}'. Install aborted (incomplete)." >&2
|
|
2134
|
+
return 1
|
|
2135
|
+
fi
|
|
2136
|
+
|
|
1837
2137
|
# Handle root agent file via in-place region update (Pillar 3).
|
|
1838
2138
|
local root_src="${staging}/${root_agent}"
|
|
1839
2139
|
local root_dst="${target}/${root_agent}"
|
|
@@ -1852,8 +2152,41 @@ install_tool() {
|
|
|
1852
2152
|
fi
|
|
1853
2153
|
fi
|
|
1854
2154
|
|
|
1855
|
-
#
|
|
1856
|
-
manifest_write
|
|
2155
|
+
# Manifest-seam entry gate (PLAN risk #3, delivery-001->002 seam).
|
|
2156
|
+
# Runs BEFORE manifest_write so a contaminated bundle never writes to disk.
|
|
2157
|
+
# Assert that the new bundle's path set does NOT contain any retired roots.
|
|
2158
|
+
# If a retired path leaked into the new manifest, fail loudly -- do not prune
|
|
2159
|
+
# against a contaminated manifest (content-isolation cornerstone).
|
|
2160
|
+
local _retired_roots=(".agents/" ".cursor/rules/" ".agent/rules/")
|
|
2161
|
+
local _rr _leaked
|
|
2162
|
+
for _rr in "${_retired_roots[@]}"; do
|
|
2163
|
+
for _p in "${install_paths[@]+"${install_paths[@]}"}"; do
|
|
2164
|
+
if [[ "$_p" == "${_rr}"* ]]; then
|
|
2165
|
+
_leaked="$_p"
|
|
2166
|
+
break
|
|
2167
|
+
fi
|
|
2168
|
+
done
|
|
2169
|
+
if [[ -n "${_leaked:-}" ]]; then
|
|
2170
|
+
echo "ERROR: aid-install-core: manifest-seam violation: retired root '${_rr}' leaked into the new bundle manifest (path: ${_leaked}). Aborting install to protect user content." >&2
|
|
2171
|
+
return 1
|
|
2172
|
+
fi
|
|
2173
|
+
done
|
|
2174
|
+
unset _rr _leaked
|
|
2175
|
+
|
|
2176
|
+
# Write manifest (merge). A failure here MUST abort loudly (parity with PS
|
|
2177
|
+
# Write-AidManifest, which throws) -- otherwise a failed manifest write is a
|
|
2178
|
+
# silent success and later `aid status`/`aid remove`/prune won't see the tool.
|
|
2179
|
+
if ! manifest_write "$manifest" "$tool" "$version" "install_paths" "root_entries"; then
|
|
2180
|
+
echo "ERROR: aid-install-core: manifest write failed for ${manifest}. Install aborted (files copied but not recorded)." >&2
|
|
2181
|
+
return 1
|
|
2182
|
+
fi
|
|
2183
|
+
|
|
2184
|
+
# Retired-root migration sweep (FR7/FR7a).
|
|
2185
|
+
# Remove AID-owned content from retired layout dirs BEFORE the normal prune,
|
|
2186
|
+
# so that old .agents/, .cursor/rules/, .agent/rules/ trees are cleaned up.
|
|
2187
|
+
# A non-zero rc from install_tool is treated as a mid-commit failure (caller
|
|
2188
|
+
# prints the "re-run to heal" message); this function returns 0 (WARN-not-fail).
|
|
2189
|
+
_migrate_retired_layout "$target" "$tool"
|
|
1857
2190
|
|
|
1858
2191
|
# Prune stale AID-owned files (Pillar 2, R7).
|
|
1859
2192
|
# Build an associative array from the new manifest path set for O(1) lookup.
|
|
@@ -1868,6 +2201,11 @@ install_tool() {
|
|
|
1868
2201
|
# Write version marker.
|
|
1869
2202
|
write_version_marker "$target" "$version"
|
|
1870
2203
|
|
|
2204
|
+
# Project-level provisioning (work-007): seed the required settings.yml and
|
|
2205
|
+
# maintain the .gitignore AID region. Both idempotent; safe per-tool.
|
|
2206
|
+
seed_settings_yml "$target" "$tool"
|
|
2207
|
+
update_gitignore "$target"
|
|
2208
|
+
|
|
1871
2209
|
# Print concise summary (always shown; per-file lines only when AID_VERBOSE=1).
|
|
1872
2210
|
local _total_files=$((_COPY_COUNT_COPIED + _COPY_COUNT_UPTODATE + _COPY_COUNT_UPDATED + _COPY_COUNT_SKIPPED))
|
|
1873
2211
|
if [[ "$_total_files" -gt 0 ]]; then
|
|
@@ -1890,6 +2228,13 @@ install_tool() {
|
|
|
1890
2228
|
fi
|
|
1891
2229
|
fi
|
|
1892
2230
|
|
|
2231
|
+
# Report project-level provisioning actions (work-007; always shown).
|
|
2232
|
+
[[ "${_CORE_SEEDED_SETTINGS:-0}" -eq 1 ]] && echo " Created .aid/settings.yml from template"
|
|
2233
|
+
case "${_CORE_GITIGNORE_ACTION:-unchanged}" in
|
|
2234
|
+
created) echo " Created .gitignore with AID exclusions" ;;
|
|
2235
|
+
updated) echo " Updated .gitignore AID exclusions" ;;
|
|
2236
|
+
esac
|
|
2237
|
+
|
|
1893
2238
|
return 0
|
|
1894
2239
|
}
|
|
1895
2240
|
|
|
@@ -1986,14 +2331,17 @@ uninstall_tool() {
|
|
|
1986
2331
|
# Remove this tool from manifest.
|
|
1987
2332
|
manifest_remove_tool "$manifest" "$tool"
|
|
1988
2333
|
|
|
1989
|
-
# If no manifest remains, remove the
|
|
2334
|
+
# If no manifest remains (last tool removed), remove the AID-provisioned
|
|
2335
|
+
# project files so a full uninstall leaves .aid/ clean (work-007).
|
|
1990
2336
|
if [[ ! -f "$manifest" ]]; then
|
|
1991
|
-
local version_marker
|
|
1992
|
-
version_marker="$(dirname "$manifest")/.aid-version"
|
|
1993
|
-
rm -f "$version_marker"
|
|
1994
|
-
# Remove .aid dir if empty.
|
|
1995
2337
|
local aid_meta_dir
|
|
1996
2338
|
aid_meta_dir="$(dirname "$manifest")"
|
|
2339
|
+
rm -f "${aid_meta_dir}/.aid-version"
|
|
2340
|
+
# Remove the install-time-seeded settings.yml (symmetric with seed_settings_yml).
|
|
2341
|
+
# Only fires when NO tools remain -- a partial uninstall keeps settings.yml
|
|
2342
|
+
# for the remaining tools.
|
|
2343
|
+
rm -f "${aid_meta_dir}/settings.yml"
|
|
2344
|
+
# Remove .aid dir if empty.
|
|
1997
2345
|
if [[ -d "$aid_meta_dir" ]]; then
|
|
1998
2346
|
local rem
|
|
1999
2347
|
rem="$(find "$aid_meta_dir" -type f 2>/dev/null | head -1)"
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aid-installer",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AID CLI installer - puts the persistent aid command on PATH via npm",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "AID (AI Integrated Development) CLI installer - puts the persistent aid command on PATH via npm",
|
|
5
|
+
"author": "Andre Vianna",
|
|
6
|
+
"homepage": "https://aid.casuloailabs.com",
|
|
5
7
|
"bin": {
|
|
6
8
|
"aid": "bin/aid.js"
|
|
7
9
|
},
|
|
@@ -18,18 +20,19 @@
|
|
|
18
20
|
"bin/",
|
|
19
21
|
"lib/",
|
|
20
22
|
"dashboard/",
|
|
21
|
-
"scripts/",
|
|
23
|
+
"scripts/postinstall.js",
|
|
22
24
|
"VERSION",
|
|
23
25
|
"README.md",
|
|
24
26
|
"LICENSE"
|
|
25
27
|
],
|
|
26
28
|
"keywords": [
|
|
27
29
|
"aid",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
+
"AI",
|
|
31
|
+
"integrated",
|
|
30
32
|
"development",
|
|
31
33
|
"cli",
|
|
32
|
-
"installer"
|
|
34
|
+
"installer",
|
|
35
|
+
"methodology"
|
|
33
36
|
],
|
|
34
37
|
"license": "MIT",
|
|
35
38
|
"repository": {
|