@zerwiz/ymir 0.1.5 → 0.1.7

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.
Files changed (118) hide show
  1. package/.agents/agents/huginn-researcher.md +1 -1
  2. package/.agents/agents/kvasir-scout.md +1 -1
  3. package/.agents/agents/sindri-developer.md +1 -1
  4. package/.agents/backend/README.md +74 -0
  5. package/.agents/backend/model-bridge.py +74 -4
  6. package/.agents/config/cron.yaml +2 -0
  7. package/.agents/config/eindri-dispatch.json +6 -6
  8. package/.agents/migrations/0003-private-data-separation.sh +11 -1
  9. package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
  10. package/.agents/skills/README.md +1 -1
  11. package/.agents/skills/galdr-ymirsystem/SKILL.md +2 -1
  12. package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +73 -10
  13. package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +44 -1
  14. package/.agents/skills/galdr-ymirsystem/assets/installation.md +215 -23
  15. package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +3 -1
  16. package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +39 -1
  17. package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +4 -0
  18. package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +7 -2
  19. package/.agents/skills/galdr-ymirsystem/assets/registry.md +1 -1
  20. package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +2 -2
  21. package/.agents/skills/galdr-ymirsystem/assets/smidja.md +16 -11
  22. package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +147 -4
  23. package/.agents/skills/groa-update/SKILL.md +20 -0
  24. package/.agents/skills/hamr-adapters/SKILL.md +2 -2
  25. package/.agents/skills/lifecycle/smoke_test.sh +7 -2
  26. package/.agents/skills/ymir-host/SKILL.md +2 -1
  27. package/.agents/skills/ymir-host/assets/install.md +69 -0
  28. package/AGENTS.md +78 -34
  29. package/CHANGELOG.md +1033 -1
  30. package/README.md +1 -1
  31. package/RULES/02-agents.md +2 -2
  32. package/RULES/04-hoard.md +32 -0
  33. package/RULES/06-append-only.md +50 -0
  34. package/RULES/07-config.md +56 -0
  35. package/RULES/09-electron.md +58 -0
  36. package/RULES/README.md +2 -0
  37. package/bin/a2a-mcp.sh +42 -7
  38. package/bin/agents-config.sh +30 -7
  39. package/bin/apodex-smoke-test.sh +14 -1
  40. package/bin/bifrost-bridge.sh +25 -6
  41. package/bin/bootstrap-macos.sh +6 -1
  42. package/bin/brokk-send.sh +14 -1
  43. package/bin/changelog-assemble.sh +154 -0
  44. package/bin/changelog-guard.sh +39 -7
  45. package/bin/crash-sense.sh +14 -1
  46. package/bin/design-check.sh +80 -0
  47. package/bin/design-icon.sh +153 -0
  48. package/bin/docs-guard.sh +2 -2
  49. package/bin/editor-place.sh +33 -15
  50. package/bin/eindri-acclaim.sh +14 -1
  51. package/bin/eindri-seat.sh +83 -0
  52. package/bin/eindri-seen.sh +14 -1
  53. package/bin/eindri-start.sh +14 -1
  54. package/bin/eir-doctor.sh +47 -1
  55. package/bin/feature-inventory.sh +73 -0
  56. package/bin/fleet-apply.sh +15 -2
  57. package/bin/github-deploy.sh +14 -1
  58. package/bin/gjallarhorn-expose.sh +43 -9
  59. package/bin/gjallarhorn-notify.sh +14 -1
  60. package/bin/gjallarhorn-purge.sh +14 -1
  61. package/bin/gjallarhorn-tunnel.sh +16 -3
  62. package/bin/groa-update.sh +9 -1
  63. package/bin/hall-snapshot.sh +21 -2
  64. package/bin/herdr-run.sh +23 -2
  65. package/bin/hlidskjalf-agents.sh +172 -0
  66. package/bin/hlidskjalf-usage.sh +196 -0
  67. package/bin/hoard-guard.sh +135 -0
  68. package/bin/hoard-lib.sh +91 -11
  69. package/bin/hodd.sh +28 -4
  70. package/bin/huginn-research-worker.sh +14 -1
  71. package/bin/local-model-lock.sh +22 -3
  72. package/bin/mimir-bridge.py +33 -0
  73. package/bin/mimir-bridge.sh +14 -1
  74. package/bin/mimir-reflect.sh +27 -0
  75. package/bin/mjollnir-webhook.sh +14 -1
  76. package/bin/mjollnir.sh +22 -3
  77. package/bin/model-hardware.sh +17 -3
  78. package/bin/model-resolve.sh +14 -1
  79. package/bin/nornir-job-hall-snapshot.sh +28 -0
  80. package/bin/nornir-job-nsr-compliance.sh +54 -0
  81. package/bin/npm-publish.sh +136 -0
  82. package/bin/omarchy-install.sh +18 -5
  83. package/bin/omarchy-plugins.sh +15 -2
  84. package/bin/omarchy-sense.sh +14 -1
  85. package/bin/pi-model-agnostic.sh +71 -0
  86. package/bin/private-guard.sh +20 -0
  87. package/bin/project-git.sh +18 -4
  88. package/bin/public-guard.sh +3 -2
  89. package/bin/sessrumnir-ensure.sh +1 -1
  90. package/bin/sessrumnir.sh +16 -3
  91. package/bin/smidja-bootstrap.sh +5 -1
  92. package/bin/syn-asset-pretool-check.sh +14 -1
  93. package/bin/syn-spawn-pretool-check.sh +58 -0
  94. package/bin/tailscale-sync.sh +15 -2
  95. package/bin/telegram-bot.sh +14 -1
  96. package/bin/ui-truth-check.sh +75 -0
  97. package/bin/valknut-load.sh +42 -4
  98. package/bin/wedge-notify.sh +14 -1
  99. package/bin/workspace-rag.sh +14 -1
  100. package/bin/ymir-install.sh +219 -54
  101. package/bin/ymir-plan.sh +356 -0
  102. package/bin/ymir-say.sh +14 -1
  103. package/bin/ymir-setup-auth.sh +12 -1
  104. package/bin/ymir-validate.sh +28 -8
  105. package/docs/lore.md +5 -1
  106. package/docs/runbooks/agents.md +1 -1
  107. package/package.json +13 -3
  108. package/scripts/electron.sh +74 -15
  109. package/scripts/lower.sh +34 -0
  110. package/scripts/raise.sh +42 -0
  111. package/scripts/start.sh +52 -2
  112. package/.agents/config/agents.yaml +0 -50
  113. package/.agents/config/tailscale-sync.yaml +0 -27
  114. package/.agents/memory/kaia.engram +0 -0
  115. package/.agents/memory/kaia.engram-shm +0 -0
  116. package/.agents/memory/kaia.engram-wal +0 -0
  117. package/.agents/memory/well/episodes.jsonl +0 -367
  118. package/.agents/memory/well/workspace.jsonl +0 -27
@@ -6,13 +6,16 @@
6
6
  #
7
7
  # Usage:
8
8
  # bin/ymir-install.sh [--check] [--skip-engines] [--skip-services] [--no-desktop] [--yes]
9
+ # bin/ymir-install.sh --plan [--json] # the plan, computed — changes nothing
9
10
  # bin/ymir-install.sh --status
10
11
  # bin/ymir-install.sh --version
11
12
  #
12
- # Consent: a real install (not --check) asks the operator to accept the plan
13
- # before any change is made. Non-interactive callers must pass --yes.
13
+ # The consent a real install asks for is not a recited paragraph: it is the PLAN
14
+ # printed by bin/ymir-plan.sh probed on THIS host, one row per step, each with
15
+ # its state and the reason for it. A paragraph drifts behind the code; a plan is
16
+ # computed from it every run.
14
17
  #
15
- # Exit: 0 all good (or --check), 1 a step failed, 2 usage, 3 declined.
18
+ # Exit: 0 all good (or --check/--plan), 1 a step failed, 2 usage, 3 declined.
16
19
  set -u
17
20
 
18
21
  # --- portability shim: bin/ymir-platform.sh --------------------------------
@@ -27,30 +30,54 @@ fi
27
30
  VERSION="1.0.0"
28
31
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
32
  ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
30
- YMIR_HOME="${YMIR_HOME:-$HOME/Documents/Ymir}"
31
- WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
33
+
34
+ # The operator's settings and secrets live in the home they chose, never in the
35
+ # code tree — a packaged install replaces its tree on upgrade, and a credential
36
+ # must never sit in a tree that ships (Rule 04).
37
+ if [ -z "${YMIR_HOARD_LIB_LOADED:-}" ]; then
38
+ _yr="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
+ for _yc in "$_yr/hoard-lib.sh" "$(dirname "$_yr")/bin/hoard-lib.sh"; do
40
+ [ -r "$_yc" ] && { . "$_yc"; YMIR_HOARD_LIB_LOADED=1; break; }
41
+ done
42
+ unset _yr _yc
43
+ fi
44
+ hoard_settings_dir YMIR_SETTINGS_DIR
45
+ hoard_local_env YMIR_ENV_FILE
46
+ # The home is the OPERATOR's to choose, never ours to assume. Resolution:
47
+ # $YMIR_HOME (explicit) → the choice recorded at a previous installation →
48
+ # the one documented default. An interactive run ASKS and RECORDS the answer
49
+ # (step_home); --check never writes, --yes takes what is recorded.
32
50
  # The hoard resolves through the shared lib, so the installer can never disagree
33
51
  # with bin/hodd.sh about where private data lives — and never points inside the
34
52
  # repo (Rule 04).
35
53
  # shellcheck source=bin/hoard-lib.sh
36
54
  . "$SCRIPT_DIR/hoard-lib.sh"
55
+ ymir_home_root YMIR_HOME
56
+ WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
37
57
  hoard_root HOARD
38
58
  DOMAINS="company marketing development life me"
39
59
 
40
- CHECK=0; SKIP_ENGINES=0; SKIP_SERVICES=0; ASSUME_YES=0; NO_DESKTOP=0
41
- case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
60
+ CHECK=0; SKIP_ENGINES=0; SKIP_SERVICES=0; ASSUME_YES=0; NO_DESKTOP=0; PLAN_ONLY=0; PLAN_ARGS=()
61
+ case "${1-}" in -v|-V|--version) printf '%s\n' "$VERSION"; exit 0 ;; -h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; esac
42
62
  while [ $# -gt 0 ]; do
43
63
  case "$1" in
44
64
  --check) CHECK=1; shift ;;
65
+ --plan|--dry-run) PLAN_ONLY=1; shift ;;
66
+ --json) PLAN_ARGS+=(--json); shift ;;
45
67
  --skip-engines) SKIP_ENGINES=1; shift ;;
46
68
  --skip-services) SKIP_SERVICES=1; shift ;;
47
69
  --no-desktop) NO_DESKTOP=1; shift ;;
48
70
  --yes|-y) ASSUME_YES=1; shift ;;
49
71
  --status) exec "$SCRIPT_DIR/ymir-install.sh" --check ;;
50
- *) printf 'error: unknown flag %s\nhelp: bin/ymir-install.sh [--check|--skip-engines|--skip-services|--no-desktop|--yes]\n' "$1" >&2; exit 2 ;;
72
+ *) printf 'error: unknown flag %s\nhelp: bin/ymir-install.sh [--check|--plan|--skip-engines|--skip-services|--no-desktop|--yes]\n' "$1" >&2; exit 2 ;;
51
73
  esac
52
74
  done
53
75
 
76
+ # The plan is a question, not a change: it prints and exits without writing.
77
+ if [ "$PLAN_ONLY" = 1 ]; then
78
+ exec "$SCRIPT_DIR/ymir-plan.sh" ${PLAN_ARGS[@]+"${PLAN_ARGS[@]}"}
79
+ fi
80
+
54
81
  declare -a IDS STATUS DETAIL
55
82
  # The code minted for this install, reported at the end so it is not lost in the
56
83
  # step table. Empty on --check, and when no code could be minted.
@@ -62,33 +89,28 @@ TOON="install[0]{step,status,detail}:"
62
89
  # ── consent ──────────────────────────────────────────────────────────────
63
90
  # Show exactly what will change and require explicit acceptance. A real
64
91
  # install touches the machine (packages, a docker image, raised services),
65
- # so it never proceeds on silence.
92
+ # so it never proceeds on silence. The plan is COMPUTED here (bin/ymir-plan.sh
93
+ # probes this host) — never a paragraph that can drift behind the code.
66
94
  confirm_install() {
67
95
  [ "$ASSUME_YES" = 1 ] && return 0
68
96
  if [ ! -t 0 ]; then
69
- printf 'error: refusing a non-interactive install without --yes\nhelp: re-run with --yes to accept non-interactively, or --check to preview\n' >&2
97
+ printf 'error: refusing a non-interactive install without --yes\nhelp: re-run with --yes to accept non-interactively, or --plan / --check to preview\n' >&2
70
98
  exit 3
71
99
  fi
72
100
  cat <<'PLAN'
73
- Ymir first setup — this will make the following changes:
74
-
75
- • install in USER SPACE (no sudo): bun, uv — and 'mcp<2>' via pip
76
- create the workspace tree (work/ · personal/ · companies · registries)
77
- install the OSS engines: treehouse, no-mistakes (+ sandcastle if present)
78
- ensure the Hermes worker runtime
79
- ensure a terminal backend (herdr Þjazi — preferred, else tmux)
80
- learn this machine (Omarchy version, packages, configs, monitors, scale)
81
- • place the desktop apps on their own numbered desktops
82
- • build the Utgard sandbox image 'utgard-runner:latest' (needs docker access)
83
- • create the Smiðja database and build the visualizer UI
84
- • load agents/skills and write workspace/INSTALL.md
85
- • install the git delivery gates (secret · branch · changelog)
86
- • raise the runtime services (Hlidskjalf SPA + gate API + bridges)
87
- • open BOTH desktop apps so you see them: Hlidskjalf + Smíðja
88
- • verify the running system and report what stands
101
+ Ymir first setup — the plan below is probed on THIS machine, not recited.
102
+ Each row is a phase, a step, its state, and why.
103
+
104
+ DO a change will be made
105
+ SKIP already satisfied nothing to do
106
+ INFO a fact about this host; no change implied
107
+ BLOCKED cannot run the reason names what is missing
108
+ CONSENT needs your word (a credential, an invite, the desktop shells)
89
109
 
90
110
  Nothing is deleted. Every step is idempotent.
91
111
  PLAN
112
+ printf '\n'
113
+ bash "$SCRIPT_DIR/ymir-plan.sh" 2>&1 || true
92
114
  printf '\nProceed with the install? [y/N] '
93
115
  read -r reply || reply=""
94
116
  case "$reply" in
@@ -197,6 +219,112 @@ YAML
197
219
  add tree OK "workspace/{personal} · companies · registries · hoard (outside the repo) (created $created)"
198
220
  }
199
221
 
222
+ # ── 2a. the home the operator chooses ────────────────────────────────────────
223
+ # Everything private — docs, secrets, identity, projects, workspaces, memory —
224
+ # lives in ONE place the operator owns, and it is the operator's to name. A real
225
+ # interactive run asks once and records the answer under ~/.config/ymir/home, so
226
+ # every later script resolves the same home without being told again. --check
227
+ # never writes; --yes takes what is already recorded (or the documented default).
228
+ step_home() {
229
+ local rec="" chosen=""
230
+ ymir_home_record rec
231
+ if [ -n "${YMIR_HOME:-}" ] && [ "$CHECK" = 1 ]; then add home OK "home: $YMIR_HOME"; return; fi
232
+ if [ -n "$rec" ]; then
233
+ add home OK "home: $YMIR_HOME (the choice recorded at installation)"
234
+ return
235
+ fi
236
+ if [ "$ASSUME_YES" = 1 ] || [ ! -t 0 ]; then
237
+ if ymir_home_record_set "$YMIR_HOME" 2>/dev/null; then
238
+ add home OK "home: $YMIR_HOME (default recorded — change it any time with YMIR_HOME=<path> bin/ymir-install.sh)"
239
+ else
240
+ add home WARN "home: $YMIR_HOME (could not record the choice under $YMIR_CONFIG_DIR — pass YMIR_HOME=<path> to every run)"
241
+ fi
242
+ return
243
+ fi
244
+ printf '\nWhere shall your home live?\n'
245
+ printf 'Everything private is kept there — docs, secrets, identity, projects,\n'
246
+ printf 'workspaces, memory — never in the code tree, never in this package.\n'
247
+ printf 'Home [%s]: ' "$YMIR_HOME"
248
+ IFS= read -r chosen || true
249
+ [ -n "$chosen" ] && YMIR_HOME="${chosen/#\~/$HOME}"
250
+ if [ -e "$YMIR_HOME" ]; then
251
+ printf 'using %s — what is already there is kept; nothing is deleted.\n' "$YMIR_HOME"
252
+ fi
253
+ # Re-resolve the roots the choice moved: the hoard within the home, and the
254
+ # workspace tree beneath it. A second run finds the record and never asks again.
255
+ hoard_root HOARD
256
+ WORKSPACE="${YMIR_WORKSPACE:-$YMIR_HOME/workspaces}"
257
+ if ymir_home_record_set "$YMIR_HOME"; then
258
+ add home OK "home: $YMIR_HOME (recorded — later runs need not ask)"
259
+ else
260
+ add home WARN "home: $YMIR_HOME (could not record the choice; pass YMIR_HOME=<path> to every run)"
261
+ fi
262
+ }
263
+
264
+ # ── 2b. app repos (the app split) ────────────────────────────────────────────
265
+ # The apps live in their OWN repos (zerwiz/hlidskjalf · hlidskjalf-mobile ·
266
+ # odrerir · sessrumnir · smidja), registered in the home registry with a
267
+ # `repo: apps/<path>` block. The monorepo never tracks them; installation
268
+ # clones each into the tree — never guessing a remote, always reading the
269
+ # registry. A present repo is fast-forwarded; a missing one cloned. The smithy
270
+ # engine (apps/smidja) is stamped from the cloned factory's templates, exactly
271
+ # as install.py does for a target repo.
272
+ step_apps() {
273
+ local reg="$HOARD/identity/projects.yaml"
274
+ if [ ! -r "$reg" ]; then
275
+ add apps SKIP "no registry — no app repos to pull ($reg)"
276
+ return 0
277
+ fi
278
+ # Parse blocks that carry `repo: apps/<path>` and a `git:` inline dict.
279
+ local entries
280
+ entries=$(awk '
281
+ /^ - id:/ { repo="" }
282
+ /^ repo: apps\// { repo=substr($2, 6) }
283
+ /^ git: / {
284
+ line=$0; host=""; owner=""; grepo=""
285
+ if (match(line, /host: [A-Za-z0-9._-]+/)) host=substr(line, RSTART+6, RLENGTH-6)
286
+ if (match(line, /owner: [A-Za-z0-9_-]+/)) owner=substr(line, RSTART+7, RLENGTH-7)
287
+ if (match(line, /repo: [A-Za-z0-9._-]+/)) grepo=substr(line, RSTART+6, RLENGTH-6)
288
+ if (repo != "" && owner != "" && grepo != "") print repo "|" host "|" owner "|" grepo
289
+ repo=""
290
+ }' "$reg")
291
+ if [ -z "$entries" ]; then
292
+ add apps OK "no apps/ projects registered — nothing to pull"
293
+ return 0
294
+ fi
295
+ local ok=0 skip=0 fail=0 missing=0
296
+ while IFS='|' read -r rel host owner grepo; do
297
+ [ -n "$rel" ] || continue
298
+ local path="$ROOT/apps/$rel"
299
+ if [ "$CHECK" = 1 ]; then
300
+ if [ -d "$path/.git" ]; then add apps OK "$rel present ($owner/$grepo)"; ok=$((ok+1));
301
+ elif [ -d "$path" ]; then add apps WARN "$rel present but not a git clone — re-install to replace"; skip=$((skip+1));
302
+ else add apps WARN "$rel absent — a full run clones $owner/$grepo"; missing=$((missing+1)); fi
303
+ continue
304
+ fi
305
+ if [ -d "$path/.git" ]; then
306
+ if (cd "$path" && git fetch origin 2>/dev/null && git merge --ff-only origin/main >/dev/null 2>&1); then add apps OK "$rel up to date ($owner/$grepo)"; ok=$((ok+1));
307
+ else add apps OK "$rel present ($owner/$grepo; pull refused — local changes?)"; skip=$((skip+1)); fi
308
+ elif [ -d "$path" ]; then
309
+ add apps WARN "$rel present but not a git clone — move $path aside and re-run"; skip=$((skip+1))
310
+ else
311
+ local url="https://github.com/$owner/$grepo.git"
312
+ if git clone -q "$url" "$path" 2>/dev/null; then add apps OK "$rel cloned ($owner/$grepo)"; ok=$((ok+1));
313
+ else add apps WARN "$rel clone failed — run: git clone $url $path"; fail=$((fail+1)); fi
314
+ fi
315
+ done <<< "$entries"
316
+ # The smithy engine: apps/smidja is stamped from the cloned factory's
317
+ # templates (the monorepo tracks neither). The config template follows.
318
+ local factory="$ROOT/apps/smidja-factory" eng="$ROOT/apps/smidja"
319
+ if [ "$CHECK" = 0 ] && [ -d "$factory/templates/smidja" ] && [ ! -e "$eng/smidja_modules" ]; then
320
+ mkdir -p "$eng"
321
+ cp -rn "$factory/templates/smidja/." "$eng/" 2>/dev/null
322
+ [ -f "$factory/templates/smidja.config.yaml" ] && { mkdir -p "$eng/smidja_smidja_config"; cp -n "$factory/templates/smidja.config.yaml" "$eng/smidja_smidja_config/smidja.config.yaml" 2>/dev/null; }
323
+ add apps OK "smidja engine stamped into apps/smidja"
324
+ fi
325
+ if [ "$fail" -gt 0 ]; then add apps WARN "$fail app clone(s) failed (offline?)"; fi
326
+ }
327
+
200
328
  # ── 3. engines ───────────────────────────────────────────────────────────────
201
329
  step_engines() {
202
330
  [ "$SKIP_ENGINES" = 1 ] && { add engines SKIP "--skip-engines"; return; }
@@ -240,7 +368,6 @@ step_models() {
240
368
  if [ ! -f "$HOME/.pi/agent/models.json" ]; then
241
369
  "$SCRIPT_DIR/models-detect.sh" --write >/dev/null 2>&1 && add models OK "seeded ~/.pi/agent/models.json (was absent)"
242
370
  fi
243
- printf 'models: local-model testing is first-class — load the modeltesting skill; llama.cpp (llama-server/llama-swap) is fastest on the GPU; research your hardware best settings online; for coding use >= 80000 context.\n'
244
371
  fi
245
372
  if [ -x "$SCRIPT_DIR/model-hardware.sh" ]; then
246
373
  "$SCRIPT_DIR/model-hardware.sh" >/dev/null 2>&1 && add models OK "profiled this machine (data/local-models.md; online research left for Brokk)"
@@ -323,35 +450,37 @@ step_omarchy() {
323
450
  }
324
451
 
325
452
  step_host() {
326
- # Core host learning: record THIS machine so the agent helps with the real one.
327
- # The Omarchy-specific extras (desktop placement, the post-update hook, the
328
- # plugin offer, the desktop alarm channel) belong to the Omarchy layer and are
329
- # applied by step_omarchy; nothing here assumes Omarchy.
330
- local learned=no
331
- if [ "$CHECK" = 1 ]; then
332
- if [ -x "$SCRIPT_DIR/omarchy-sense.sh" ]; then
333
- local snap; snap="$("$SCRIPT_DIR/omarchy-sense.sh" status 2>&1 | sed -n '2p' | tr -d '"' | cut -c1-60)"
334
- learned="${snap:-no snapshot yet}"
335
- fi
336
- add host OK "host snapshot: ${learned}"
337
- else
338
- if [ -x "$SCRIPT_DIR/omarchy-sense.sh" ]; then
339
- "$SCRIPT_DIR/omarchy-sense.sh" observe --quiet >/dev/null 2>&1 && learned=yes
340
- fi
341
- add host OK "host learnt=${learned}"
453
+ # Core host learning: sense THIS machine on EVERY host with the portable
454
+ # sensor (Rule 05). The Omarchy-specific RECORDING (omarchy-sense observe), the
455
+ # post-update hook, desktop placement, the plugin offer, and the desktop alarm
456
+ # channel belong to the Omarchy layer and are applied by step_omarchy; nothing
457
+ # here assumes Omarchy.
458
+ local seen="unknown"
459
+ if [ -x "$SCRIPT_DIR/host-sense.sh" ]; then
460
+ # host-sense TOON row 2 is: os, id, family, session, desktop
461
+ local row; row="$("$SCRIPT_DIR/host-sense.sh" 2>/dev/null | sed -n '2p')"
462
+ [ -n "$row" ] && seen="$(printf '%s' "$row" | sed -E 's/^[[:space:]]*"//; s/"[[:space:]]*$//; s/","/ \/ /g')"
342
463
  fi
343
-
344
- # The Allfather's own agent setup, seeded from the tracked template. Idempotent:
345
- # config/agents.yaml is private and is never overwritten once set. --check never
346
- # writes, so it only reports.
347
- if [ -e "$ROOT/config/agents.yaml" ]; then
464
+ add host OK "host sensed: ${seen}"
465
+
466
+ # The operator's own agent setup, seeded from the tracked template into the
467
+ # home's settings dir — settings are the operator's, never the package's.
468
+ # Idempotent: the seeded file is private and is never overwritten once set.
469
+ # --check writes nothing, so it only reports. The template is code, so it is
470
+ # read from wherever the tree keeps it: `config/` in a clone (a symlink the
471
+ # npm tarball cannot carry), else `.agents/config/`.
472
+ if [ -e "$YMIR_SETTINGS_DIR/agents.yaml" ]; then
348
473
  add agents-config OK "kept (private, never overwritten)"
349
- elif [ "$CHECK" = 1 ]; then
350
- add agents-config WARN "not seeded (run without --check)"
351
- elif [ -r "$ROOT/config/agents.yaml.example" ] && cp "$ROOT/config/agents.yaml.example" "$ROOT/config/agents.yaml" 2>/dev/null; then
352
- add agents-config OK "seeded from the template"
474
+ return 0
475
+ fi
476
+ local tpl="$ROOT/config/agents.yaml.example"
477
+ [ -r "$tpl" ] || tpl="$ROOT/.agents/config/agents.yaml.example"
478
+ if [ "$CHECK" = 1 ]; then
479
+ add agents-config WARN "not seeded yet — a real run writes $YMIR_SETTINGS_DIR/agents.yaml"
480
+ elif [ -r "$tpl" ] && mkdir -p "$YMIR_SETTINGS_DIR" && cp "$tpl" "$YMIR_SETTINGS_DIR/agents.yaml" 2>/dev/null; then
481
+ add agents-config OK "seeded $YMIR_SETTINGS_DIR/agents.yaml from the template"
353
482
  else
354
- add agents-config WARN "could not seed config/agents.yaml"
483
+ add agents-config WARN "could not seed $YMIR_SETTINGS_DIR/agents.yaml"
355
484
  fi
356
485
  }
357
486
 
@@ -487,12 +616,44 @@ step_gates() {
487
616
  else
488
617
  add gates WARN "hooks not installed — bin/secret-guard.sh --install && bin/changelog-guard.sh --install"
489
618
  fi
619
+ # The home repo is where the private data lives, so its ward matters more
620
+ # than this repo's four. Report it separately so a dormant vault is visible.
621
+ local hh; hh="${YMIR_HOME:-$HOME/Documents/Ymir}/.git/hooks/pre-commit"
622
+ if [ -x "$hh" ]; then add hoard-gate OK "home pre-commit seated";
623
+ else add hoard-gate WARN "home hooks not installed — bin/hoard-guard.sh --install"; fi
490
624
  return
491
625
  fi
492
626
  [ -x "$SCRIPT_DIR/secret-guard.sh" ] && "$SCRIPT_DIR/secret-guard.sh" --install >/dev/null 2>&1 || true
493
627
  [ -x "$SCRIPT_DIR/changelog-guard.sh" ] && "$SCRIPT_DIR/changelog-guard.sh" --install >/dev/null 2>&1 || true
628
+ # Seat the ward on the PRIVATE home too — every install layer, every machine.
629
+ [ -x "$SCRIPT_DIR/hoard-guard.sh" ] && "$SCRIPT_DIR/hoard-guard.sh" --install >/dev/null 2>&1 || true
494
630
  if [ -x "$pre_commit" ] && [ -x "$pre_push" ]; then add gates OK "pre-commit + pre-push installed"
495
631
  else add gates WARN "could not write .git/hooks — gates are dormant"; fi
632
+ local hh; hh="${YMIR_HOME:-$HOME/Documents/Ymir}/.git/hooks/pre-commit"
633
+ if [ -x "$hh" ]; then add hoard-gate OK "home pre-commit seated";
634
+ else add hoard-gate WARN "home hooks not seated — bin/hoard-guard.sh --install"; fi
635
+ }
636
+
637
+ # ── 6c. desktop marks (the rune, the entry, the contract) ────────────────────
638
+ #
639
+ # Every app wears its OWN rune in the operator's desktop: the icon into the icon
640
+ # theme, the .desktop entry into their applications dir (so every app is
641
+ # dockable and pinnable), and the Ymir contract deployed into pi's agent home so
642
+ # every session - in ANY folder - loads Brokk. Idempotent, and it writes to the
643
+ # operator's real data dir, never a sandbox one.
644
+ step_marks() {
645
+ if [ ! -x "$SCRIPT_DIR/design-icon.sh" ]; then add marks SKIP "no design-icon.sh"; return; fi
646
+ if [ "$CHECK" = 1 ]; then
647
+ n=$(ls "$HOME/.local/share"/applications/ymir-*.desktop 2>/dev/null | wc -l | tr -d ' ')
648
+ add marks OK "$n desktop app marks installed"
649
+ return
650
+ fi
651
+ "$SCRIPT_DIR/design-icon.sh" mint --all >/dev/null 2>&1 || true
652
+ n=$("$SCRIPT_DIR/design-icon.sh" install 2>/dev/null | grep -c '"ymir-') || n=0
653
+ [ -r "$HOME/.pi/agent/AGENTS.md" ] || [ -d "$HOME/.pi/agent" ] && {
654
+ ln -sfn "$ROOT/AGENTS.md" "$HOME/.pi/agent/AGENTS.md" 2>/dev/null || true
655
+ }
656
+ add marks OK "$n app marks (rune icon + entry) · Ymir contract in the pi agent home"
496
657
  }
497
658
 
498
659
  # ── 7. services ──────────────────────────────────────────────────────────────
@@ -624,7 +785,11 @@ step_panes() {
624
785
  # Ask before touching the machine; --check only previews and never asks.
625
786
  [ "$CHECK" = 0 ] && confirm_install
626
787
 
627
- step_panes; step_prereqs; step_tree; step_engines; step_models; step_hermes; step_sessrumnir; step_backend; step_host; step_sandbox; step_memory; step_smidja; step_spa; step_omarchy; step_loaders; step_gates; bin/ymir-migrate.sh apply >/dev/null 2>&1 || true; step_auth; step_invite; step_register
788
+ step_panes; step_prereqs; step_home; step_tree; step_apps; step_engines; step_models; step_hermes; step_sessrumnir; step_backend; step_host; step_sandbox; step_memory; step_smidja; step_spa; step_omarchy; step_loaders; step_gates; step_marks
789
+ # Migrations MOVE private data — that is a write, and `--check` promises none.
790
+ # Only a real run heals the home forward; the preview leaves it untouched.
791
+ if [ "$CHECK" = 0 ]; then bin/ymir-migrate.sh apply >/dev/null 2>&1 || true; fi
792
+ step_auth; step_invite; step_register
628
793
  [ "$CHECK" = 0 ] && step_services
629
794
  [ "$CHECK" = 0 ] && step_desktop
630
795
  [ "$CHECK" = 0 ] && step_validate