@indigoai-us/hq-cloud 6.1.0 → 6.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.
Files changed (56) hide show
  1. package/dist/bin/sync-runner.d.ts.map +1 -1
  2. package/dist/bin/sync-runner.js +18 -0
  3. package/dist/bin/sync-runner.js.map +1 -1
  4. package/dist/cli/index.d.ts +2 -2
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +2 -2
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/reindex.d.ts +4 -11
  9. package/dist/cli/reindex.d.ts.map +1 -1
  10. package/dist/cli/reindex.js +336 -30
  11. package/dist/cli/reindex.js.map +1 -1
  12. package/dist/cli/reindex.test.d.ts +3 -3
  13. package/dist/cli/reindex.test.js +36 -11
  14. package/dist/cli/reindex.test.js.map +1 -1
  15. package/dist/cli/rescue-core.d.ts +36 -0
  16. package/dist/cli/rescue-core.d.ts.map +1 -0
  17. package/dist/cli/rescue-core.js +1536 -0
  18. package/dist/cli/rescue-core.js.map +1 -0
  19. package/dist/cli/rescue-drift-reconcile.test.js +33 -10
  20. package/dist/cli/rescue-drift-reconcile.test.js.map +1 -1
  21. package/dist/cli/rescue-mtime-preserve.test.js +36 -12
  22. package/dist/cli/rescue-mtime-preserve.test.js.map +1 -1
  23. package/dist/cli/rescue.d.ts +4 -10
  24. package/dist/cli/rescue.d.ts.map +1 -1
  25. package/dist/cli/rescue.js +14 -37
  26. package/dist/cli/rescue.js.map +1 -1
  27. package/dist/cli/rescue.reindex.test.js +9 -8
  28. package/dist/cli/rescue.reindex.test.js.map +1 -1
  29. package/dist/cli/rescue.test.js +1 -10
  30. package/dist/cli/rescue.test.js.map +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/conflict-index.d.ts +40 -0
  36. package/dist/lib/conflict-index.d.ts.map +1 -1
  37. package/dist/lib/conflict-index.js +121 -0
  38. package/dist/lib/conflict-index.js.map +1 -1
  39. package/dist/lib/conflict.test.js +145 -1
  40. package/dist/lib/conflict.test.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/bin/sync-runner.ts +18 -0
  43. package/src/cli/index.ts +2 -2
  44. package/src/cli/reindex.test.ts +45 -12
  45. package/src/cli/reindex.ts +345 -36
  46. package/src/cli/rescue-core.ts +1650 -0
  47. package/src/cli/rescue-drift-reconcile.test.ts +33 -12
  48. package/src/cli/rescue-mtime-preserve.test.ts +36 -15
  49. package/src/cli/rescue.reindex.test.ts +9 -8
  50. package/src/cli/rescue.test.ts +1 -11
  51. package/src/cli/rescue.ts +15 -40
  52. package/src/index.ts +2 -2
  53. package/src/lib/conflict-index.ts +146 -0
  54. package/src/lib/conflict.test.ts +171 -0
  55. package/scripts/reindex.sh +0 -318
  56. package/scripts/replace-rescue.sh +0 -1522
@@ -1,318 +0,0 @@
1
- #!/bin/bash
2
- # reindex.sh — surfaces namespace skills as Claude Code skills under
3
- # .claude/skills/<ns>:<skill>/ with every file in the source skill folder
4
- # mirrored as a symlink. Also mirrors personal/{knowledge,policies,workers,
5
- # settings}/* into core/<type>/<name>.
6
- #
7
- # This script ships inside the @indigoai-us/hq-cloud package and is invoked via
8
- # `hq reindex` (which passes the HQ root as $1). It is idempotent and cheap
9
- # to re-run, so it doesn't gate on whether personal/ was actually touched. Real
10
- # files/dirs already at the link path are left untouched.
11
- #
12
- # REPO_ROOT resolution (in priority order):
13
- # 1. $1 — passed by `hq reindex` (the HQ root)
14
- # 2. $HQ_REPO_ROOT — env override
15
- # 3. $PWD — fallback when invoked directly
16
- #
17
- # Sources surfaced as skills under .claude/skills/<namespace>:<skill>/
18
- # (each contains a symlink per source file, including SKILL.md):
19
- # companies/<slug>/skills/<skill>/* → .claude/skills/<slug>:<skill>/*
20
- # core/skills/<skill>/* → .claude/skills/core:<skill>/*
21
- # personal/skills/<skill>/* → .claude/skills/personal:<skill>/*
22
- # core/packages/<pack>/skills/<skill>/* → .claude/skills/<pack>:<skill>/*
23
- #
24
- # Namespace folders are created lazily. Skill folders starting with '.' or
25
- # '_' (e.g. _shared, _template) are skipped. Dotfiles inside a skill folder
26
- # (e.g. .DS_Store, .git) are not mirrored.
27
- #
28
- # Cleanup performed each run:
29
- # 1. Legacy .claude/commands/<ns>/<skill>.md symlinks created by a prior
30
- # version of this script are removed. Non-symlink files left alone.
31
- # Empty .claude/commands/<ns>/ dirs are rmdir'd.
32
- # 2. Stale entries inside a wrapper (symlinks whose source file no longer
33
- # exists) are pruned.
34
- # 3. Orphan .claude/skills/<ns>:<skill>/ wrappers (where <ns> is one of
35
- # the namespaces we manage but the source skill is gone) are deleted.
36
- #
37
- # Collision: if the link path already exists as a non-symlink, log + skip.
38
-
39
- set -uo pipefail
40
-
41
- REPO_ROOT="${1:-${HQ_REPO_ROOT:-$PWD}}"
42
- if [ ! -d "$REPO_ROOT" ]; then
43
- echo "reindex: REPO_ROOT '$REPO_ROOT' is not a directory" >&2
44
- exit 1
45
- fi
46
- REPO_ROOT="$(cd "$REPO_ROOT" && pwd)"
47
-
48
- mkdir -p "$REPO_ROOT/.claude/skills"
49
-
50
- # Build (namespace, src_rel) pairs.
51
- namespaces=()
52
- src_rels=()
53
-
54
- add_ns() {
55
- local ns="$1" src_rel="$2"
56
- [ -d "$REPO_ROOT/$src_rel" ] || return 0
57
- namespaces+=("$ns")
58
- src_rels+=("$src_rel")
59
- }
60
-
61
- for company_dir in "$REPO_ROOT"/companies/*/; do
62
- [ -d "$company_dir" ] || continue
63
- slug="$(basename "${company_dir%/}")"
64
- add_ns "$slug" "companies/$slug/skills"
65
- done
66
-
67
- add_ns "core" "core/skills"
68
- add_ns "personal" "personal/skills"
69
-
70
- for pack_dir in "$REPO_ROOT"/core/packages/*/; do
71
- [ -d "$pack_dir" ] || continue
72
- pack="$(basename "${pack_dir%/}")"
73
- add_ns "$pack" "core/packages/$pack/skills"
74
- done
75
-
76
- # --- Cleanup pass A: drop legacy .claude/commands/<ns>/<skill>.md symlinks ---
77
- # Scan ALL .claude/commands/*/ namespace dirs (not just ones whose source
78
- # root currently exists). Remove any *.md symlink whose target follows the
79
- # legacy bridge pattern. This handles namespaces whose entire source root
80
- # was deleted between runs — e.g. an archived company or removed pack —
81
- # which otherwise leave broken slash-command symlinks behind.
82
- # Manual or unrelated *.md files (non-symlinks, or symlinks pointing
83
- # elsewhere) are preserved.
84
- if [ -d "$REPO_ROOT/.claude/commands" ]; then
85
- for cmd_ns_dir in "$REPO_ROOT/.claude/commands"/*/; do
86
- [ -d "$cmd_ns_dir" ] || continue
87
- cmd_ns_dir="${cmd_ns_dir%/}"
88
- # Don't touch folder-level symlinks (could be legacy structure from an
89
- # even older script version — let a human resolve).
90
- [ -L "$cmd_ns_dir" ] && continue
91
-
92
- for f in "$cmd_ns_dir"/*.md; do
93
- [ -L "$f" ] || continue
94
- target="$(readlink "$f")"
95
- case "$target" in
96
- ../../../companies/*/skills/*/SKILL.md|\
97
- ../../../core/skills/*/SKILL.md|\
98
- ../../../personal/skills/*/SKILL.md|\
99
- ../../../core/packages/*/skills/*/SKILL.md)
100
- rm "$f"
101
- ;;
102
- esac
103
- done
104
-
105
- # rmdir if empty; ignore "directory not empty"
106
- rmdir "$cmd_ns_dir" 2>/dev/null || true
107
- done
108
- fi
109
-
110
- # --- Skill wrapper creation ---
111
- # Track which <ns>:<skill> wrappers we maintained this run, for orphan
112
- # cleanup at the end.
113
- expected_wrappers=()
114
-
115
- i=0
116
- seen=()
117
- while [ "$i" -lt "${#namespaces[@]}" ]; do
118
- ns="${namespaces[$i]}"
119
- src_rel="${src_rels[$i]}"
120
- i=$((i + 1))
121
-
122
- # First writer for a namespace wins.
123
- already=0
124
- for s in ${seen[@]+"${seen[@]}"}; do
125
- if [ "$s" = "$ns" ]; then
126
- already=1
127
- break
128
- fi
129
- done
130
- if [ "$already" -eq 1 ]; then
131
- echo "reindex: namespace '$ns' already claimed by an earlier source; skipping $src_rel" >&2
132
- continue
133
- fi
134
- seen+=("$ns")
135
-
136
- for skill_path in "$REPO_ROOT/$src_rel"/*; do
137
- [ -d "$skill_path" ] || continue
138
- skill_name="$(basename "$skill_path")"
139
- case "$skill_name" in
140
- .*|_*) continue ;;
141
- esac
142
- [ -f "$skill_path/SKILL.md" ] || continue
143
-
144
- wrapper_name="$ns:$skill_name"
145
- wrapper="$REPO_ROOT/.claude/skills/$wrapper_name"
146
- expected_wrappers+=("$wrapper_name")
147
-
148
- # If something non-directory occupies the slot, bail.
149
- if [ -e "$wrapper" ] && [ ! -L "$wrapper" ] && [ ! -d "$wrapper" ]; then
150
- echo "reindex: $wrapper exists and is not a directory; skipping" >&2
151
- continue
152
- fi
153
- # If it's a symlink (e.g. legacy directory-symlink form from earlier
154
- # experimentation), replace with a real directory of per-file symlinks.
155
- if [ -L "$wrapper" ]; then
156
- rm "$wrapper"
157
- fi
158
- mkdir -p "$wrapper"
159
-
160
- # Symlink every (non-hidden) entry in the source skill folder into the
161
- # wrapper. Wrapper lives at .claude/skills/<ns>:<skill>/, three levels
162
- # below REPO_ROOT.
163
- for entry_path in "$skill_path"/*; do
164
- [ -e "$entry_path" ] || continue
165
- entry="$(basename "$entry_path")"
166
- link_path="$wrapper/$entry"
167
- relative_target="../../../$src_rel/$skill_name/$entry"
168
-
169
- if [ -L "$link_path" ]; then
170
- current="$(readlink "$link_path")"
171
- if [ "$current" = "$relative_target" ]; then
172
- continue
173
- fi
174
- echo "reindex: .claude/skills/$wrapper_name/$entry already points to '$current' (expected '$relative_target'); leaving alone" >&2
175
- continue
176
- elif [ -e "$link_path" ]; then
177
- echo "reindex: $link_path already exists and is not a symlink; skipping" >&2
178
- continue
179
- fi
180
-
181
- ln -s "$relative_target" "$link_path"
182
- done
183
-
184
- # Prune symlinks in the wrapper whose source entry no longer exists.
185
- # -e on a symlink dereferences; a stale link fails -e.
186
- for link_path in "$wrapper"/*; do
187
- [ -L "$link_path" ] || continue
188
- [ -e "$link_path" ] && continue
189
- rm "$link_path"
190
- done
191
- done
192
- done
193
-
194
- # --- Cleanup pass B: drop orphan <ns>:<skill> wrappers ---
195
- # A wrapper is an orphan if its <ns> belongs to a managed namespace but the
196
- # source skill folder no longer exists (so we didn't add it to
197
- # expected_wrappers this run). Unmanaged-namespace entries are left alone —
198
- # users can hand-author <ns>:<name> wrappers and we won't clobber them.
199
- for entry_path in "$REPO_ROOT/.claude/skills"/*; do
200
- # Accept entries that exist OR are broken symlinks. A bare -e check
201
- # would skip dangling symlink wrappers, which are exactly the orphans
202
- # this pass needs to remove.
203
- [ -e "$entry_path" ] || [ -L "$entry_path" ] || continue
204
- entry="$(basename "$entry_path")"
205
- case "$entry" in
206
- *:*) ;;
207
- *) continue ;; # not a namespaced wrapper
208
- esac
209
-
210
- # Skip wrappers we maintained this run.
211
- is_expected=0
212
- for w in ${expected_wrappers[@]+"${expected_wrappers[@]}"}; do
213
- if [ "$w" = "$entry" ]; then
214
- is_expected=1
215
- break
216
- fi
217
- done
218
- if [ "$is_expected" -eq 1 ]; then
219
- continue
220
- fi
221
-
222
- # Detect whether this wrapper was produced by this script. Required:
223
- # the wrapper's namespace prefix MUST match the namespace encoded in its
224
- # symlink target. That distinguishes script-produced wrappers
225
- # (e.g. personal:foo -> personal/skills/foo) from hand-authored composite
226
- # wrappers (e.g. vendor:tool -> core/skills/some-helper, where 'vendor'
227
- # is not a namespace we manage). Without the cross-check we'd clobber
228
- # the user's hand-authored entries.
229
- #
230
- # Two wrapper shapes need to be handled:
231
- # (a) entry_path itself is a symlink — directory-style wrapper from an
232
- # older script version. Target uses 2-level relative paths.
233
- # (b) entry_path is a real directory containing per-file symlinks —
234
- # current shape. Each inner symlink uses 3-level relative paths.
235
- ns="${entry%%:*}"
236
- is_managed=0
237
- match_target() {
238
- # Args: $1=target, $2=relative prefix (../.. or ../../..)
239
- # Returns 0 if target matches the expected pattern for $ns.
240
- local t="$1" p="$2"
241
- case "$t" in
242
- "$p"/personal/skills/*) [ "$ns" = "personal" ] && return 0 ;;
243
- "$p"/core/skills/*) [ "$ns" = "core" ] && return 0 ;;
244
- "$p"/companies/"$ns"/skills/*) return 0 ;;
245
- "$p"/core/packages/"$ns"/skills/*) return 0 ;;
246
- esac
247
- return 1
248
- }
249
- if [ -L "$entry_path" ]; then
250
- t="$(readlink "$entry_path")"
251
- if match_target "$t" "../.."; then
252
- is_managed=1
253
- fi
254
- else
255
- for f in "$entry_path"/*; do
256
- [ -L "$f" ] || continue
257
- t="$(readlink "$f")"
258
- if match_target "$t" "../../.."; then
259
- is_managed=1
260
- break
261
- fi
262
- done
263
- fi
264
- if [ "$is_managed" -eq 0 ]; then
265
- continue
266
- fi
267
-
268
- # It's a managed-namespace wrapper with no corresponding live source → drop.
269
- if [ -L "$entry_path" ]; then
270
- rm "$entry_path"
271
- elif [ -d "$entry_path" ]; then
272
- rm -rf "$entry_path"
273
- fi
274
- done
275
-
276
- # --- Personal type mirroring (unchanged from prior version) ---
277
- # Mirror personal/<type>/<entry> into core/<type>/<entry> as symlinks.
278
- # .gitkeep and dotfiles are ignored.
279
- for type in knowledge policies workers settings; do
280
- personal_dir="$REPO_ROOT/personal/$type"
281
- core_dir="$REPO_ROOT/core/$type"
282
-
283
- [ -d "$personal_dir" ] || continue
284
- mkdir -p "$core_dir"
285
-
286
- for entry_path in "$personal_dir"/*; do
287
- [ -e "$entry_path" ] || continue
288
- entry="$(basename "$entry_path")"
289
- case "$entry" in
290
- .*) continue ;;
291
- esac
292
-
293
- link_path="$core_dir/$entry"
294
- relative_target="../../personal/$type/$entry"
295
-
296
- if [ -L "$link_path" ]; then
297
- current="$(readlink "$link_path")"
298
- if [ "$current" = "$relative_target" ]; then
299
- continue
300
- fi
301
- echo "reindex: core/$type/$entry already points to '$current' (expected '$relative_target'); leaving alone" >&2
302
- continue
303
- elif [ -e "$link_path" ]; then
304
- echo "reindex: core/$type/$entry already exists and is not a symlink; skipping" >&2
305
- continue
306
- fi
307
-
308
- ln -s "$relative_target" "$link_path"
309
- done
310
- done
311
-
312
- # --- Workers registry regeneration ---
313
- # Source of truth: each worker.yaml. Registry is a derived index — regenerated
314
- # here so it stays in sync with worker.yaml edits. Idempotent — only writes
315
- # when generated content differs.
316
- if [ -x "$REPO_ROOT/core/scripts/generate-workers-registry.sh" ]; then
317
- "$REPO_ROOT/core/scripts/generate-workers-registry.sh" >&2 2>&1 || true
318
- fi