Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,5 @@ python scripts/neon_genie.py do run --recipe commercial --out out/neon-genie/aud
<div align="center">
<sub>Neon Genie v3.26.0 · advice only · evidence before invention</sub>
</div>

Source, profile, and migration rules: `references/source-and-upgrades.md`.
2 changes: 2 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,3 +630,5 @@ Hermes Hub installs only `SKILL.md` plus **explicitly path-referenced** files un
<!-- END HUB_SUPPORT_FILES -->

Full tree also keeps root schemas, profiles, evals, VERSION, and manifest for clone/`./install.sh` installs (scripts resolve either layout via `scripts/paths.py`).

Source, profile, and migration rules: `references/source-and-upgrades.md`.
51 changes: 22 additions & 29 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,30 @@

set -euo pipefail

TARGET_BASE="${HOME}/.hermes/skills"
TARGET_BASE="${HERMES_HOME:-$HOME/.hermes}/skills"
DEST="${TARGET_BASE}/neon-genie"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

DRY_RUN=0
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--target) [[ $# -ge 2 ]] || { printf 'Missing target\n' >&2; exit 2; }; DEST="$2"; shift 2 ;;
--target=*) DEST="${1#--target=}"; shift ;;
-h|--help) printf 'Usage: install.sh [--target DIR] [--dry-run]\n'; exit 0 ;;
*) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;;
esac
done
python3 - "$DEST" <<'PY'
import sys
from pathlib import Path
p = Path(sys.argv[1]).expanduser().absolute()
if not sys.argv[1].strip() or any(q.is_symlink() for q in (p, *p.parents)):
raise SystemExit("refusing empty or symlinked target path")
PY
echo "Installing Neon Genie to: ${DEST}"
mkdir -p "${TARGET_BASE}"

if [ -d "${DEST}" ]; then
echo "Existing installation found. Backing up to ${DEST}.bak"
rm -rf "${DEST}.bak"
mv "${DEST}" "${DEST}.bak"
fi

mkdir -p "${DEST}"
# Copy skill tree; exclude VCS metadata
if command -v rsync >/dev/null 2>&1; then
rsync -a --exclude '.git' --exclude '.gitignore' "${ROOT}/" "${DEST}/"
else
tar -C "${ROOT}" --exclude '.git' -cf - . | tar -C "${DEST}" -xf -
if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY RUN: no files changed"
exit 0
fi

chmod +x "${DEST}/scripts/"*.py 2>/dev/null || true
chmod +x "${DEST}/install.sh" 2>/dev/null || true

echo ""
echo "Neon Genie installed successfully."
echo "Location: ${DEST}"
echo ""
echo "Next steps:"
echo " 1. Restart Hermes or reload skills."
echo " 2. Validate: python ${DEST}/scripts/validate_hermes_skill.py"
echo " 3. Try triggers like: 'product audit', 'zero option', 'wayfinder handoff'"
echo ""
echo "The skill works standalone inside Hermes (advisory only)."
exec python3 "${ROOT}/scripts/install_transaction.py" "$ROOT" "$DEST" neon-genie
58 changes: 58 additions & 0 deletions references/source-and-upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Source identity and upgrades

This distribution is `scrimshawlife-ctrl/NeonGenie`, version `3.26.0`, based on commit
`9e7e6d388a77a9f6103fba6f982457afc873084b` before the local audit-fix commit. Record the actual installed commit
with `git rev-parse HEAD` before installation; do not use a version string alone
as a source identity. Root and hub packaging are distribution surfaces, not a
claim that organizational, personal, or legacy embedded variants are identical.
No personal version is deprecated by this change.

Before switching sources, stop runtime writers, record current source/commit,
review the destination under `${HERMES_HOME:-$HOME/.hermes}`, compare contracts,
and retain a separate backup of outputs, sessions, and local customization.
Do not install two different contracts with the same skill name into one profile.
A successful compatibility check does not grant deployment/publication authority.

The bundled LICENSE controls this distribution. No license grant is changed by
these fixes; earlier grants and third-party notices remain intact.

The installer validates a fresh stage on the target filesystem before replacement,
then reads back the activated contract/version/provenance receipt. Checks use an
isolated temporary home. Legacy `out` contents (including an out symlink) survive.
Backups are unique, keyed by canonical destination under the active Hermes home's
`backups/<skill>/<target-hash>/`; `.install-provenance.json` records source, base
commit, dirty status, version, destination, and skipped checks. Stop writers during
upgrades. Two renames have an absent-target window: this is NOT crash-atomic.
On a reported recovery failure, retain the printed recovery directory and backup;
do not delete it or retry blindly. Inspect the exact target and restore from the
named backup only after validating it. No automatic source migration is implied.

## Interrupted installs and stale locks

SIGKILL or power loss can leave `.<target-name>.install-lock` beside the target.
Locks are never automatically reclaimed: age, an empty directory, or a reused PID
cannot prove that no installer is active. To recover:

1. Stop install launchers and runtime writers. Confirm no installer is active on
this target (including other sessions/hosts sharing the filesystem).
2. Inspect the exact target, sibling `.<skill>-stage-*` recovery directories
(especially `previous` and `failed-package`), and target-keyed backups. Retain
all recovery data until the original installation and outputs are accounted
for. Restore/validate a complete package first if activation was interrupted.
3. Only then set `lock` to the exact path printed in the error and run
`rmdir -- "$lock"`. This removes only an empty lock; never use recursive
deletion or remove a lock whose owner/activity is uncertain. Keep launchers
stopped through inspection and removal to avoid races, then retry installation.

Backup copies are staged in `.backup-incomplete-*` outside the target's selectable
backup directory and published by rename after copying and writing the destination
record. Hard termination can leave these incomplete staging trees; never select
one for rollback. Inspect them manually after quiescing writers. Rollback skips
legacy partial entries without a valid matching destination record.

Git is optional. Archive sources, failed Git lookups, and unrelated enclosing
worktrees record unknown Git fields as JSON `null`, never a false clean claim.
A source-root worktree or the tracked `skills/neon-genie` hub with matching root
contract/version supplies Git provenance. Receipts distinguish
`source_repository_root` from `source_subdirectory` (`.` or `skills/neon-genie`).
A dirty status lookup failure remains `null` even in a recognized worktree.
Loading
Loading