@indigoai-us/hq-cli 5.275.0 → 5.277.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 (120) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +12 -0
  3. package/assets/bot-workers/setup/context/USER-GUIDE.md +4 -4
  4. package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
  5. package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
  6. package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
  7. package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
  8. package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
  9. package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
  10. package/dist/command-catalog.generated.d.ts +239 -1
  11. package/dist/command-catalog.generated.js +302 -1
  12. package/dist/commands/core-checkpoint.d.ts +4 -2
  13. package/dist/commands/core-checkpoint.js +34 -3
  14. package/dist/commands/core-command-catalog.d.ts +4 -2
  15. package/dist/commands/core-command-catalog.js +5 -2
  16. package/dist/commands/core-delegate.d.ts +6 -0
  17. package/dist/commands/core-delegate.js +987 -0
  18. package/dist/commands/core-ontology.js +3 -3
  19. package/dist/commands/core.js +11 -1
  20. package/dist/commands/daemon.d.ts +4 -0
  21. package/dist/commands/daemon.js +175 -2
  22. package/dist/commands/dm.d.ts +2 -0
  23. package/dist/commands/dm.js +11 -4
  24. package/dist/commands/feedback.d.ts +6 -0
  25. package/dist/commands/feedback.js +86 -37
  26. package/dist/commands/lanes-accounts.js +44 -2
  27. package/dist/commands/lanes-message.js +69 -12
  28. package/dist/commands/lanes-resume.js +2 -0
  29. package/dist/commands/lanes-shared.d.ts +3 -0
  30. package/dist/commands/lanes-shared.js +12 -2
  31. package/dist/commands/lanes.d.ts +5 -1
  32. package/dist/commands/lanes.js +402 -49
  33. package/dist/commands/run-feature-telemetry.d.ts +3 -0
  34. package/dist/commands/run-feature-telemetry.js +6 -0
  35. package/dist/commands/run.js +3 -2
  36. package/dist/commands/secrets.d.ts +16 -1
  37. package/dist/commands/secrets.js +80 -45
  38. package/dist/commands/sync-mode.d.ts +11 -0
  39. package/dist/commands/sync-mode.js +30 -0
  40. package/dist/commands/whoami.d.ts +4 -6
  41. package/dist/commands/whoami.js +39 -8
  42. package/dist/fast-core.d.ts +6 -7
  43. package/dist/fast-core.js +22 -8
  44. package/dist/fast-scaffold.d.ts +33 -7
  45. package/dist/fast-scaffold.js +104 -8
  46. package/dist/index.js +32 -1
  47. package/dist/lanes-drain-hook.js +154 -7
  48. package/dist/lib/agent-kit/run/inbox.js +3 -2
  49. package/dist/lib/billing/plan-lock.d.ts +3 -1
  50. package/dist/lib/billing/plan-lock.js +7 -1
  51. package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
  52. package/dist/lib/checkpoint-ontology/apply.js +60 -3
  53. package/dist/lib/checkpoint-ontology/company.d.ts +8 -4
  54. package/dist/lib/checkpoint-ontology/company.js +15 -7
  55. package/dist/lib/checkpoint-ontology/plan.d.ts +36 -0
  56. package/dist/lib/checkpoint-ontology/plan.js +66 -4
  57. package/dist/lib/checkpoint-ontology/queued-touches.d.ts +32 -0
  58. package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
  59. package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
  60. package/dist/lib/checkpoint-ontology/tree.js +34 -0
  61. package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
  62. package/dist/lib/core-utils/jq-json-serializer.js +10 -0
  63. package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
  64. package/dist/lib/core-utils/secret-patterns.js +33 -0
  65. package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
  66. package/dist/lib/core-utils/timeout-guard-native.js +40 -0
  67. package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
  68. package/dist/lib/core-utils/timeout-guard.js +28 -13
  69. package/dist/lib/daemon/catalog.d.ts +18 -2
  70. package/dist/lib/daemon/catalog.js +18 -2
  71. package/dist/lib/daemon/config.d.ts +6 -0
  72. package/dist/lib/daemon/config.js +15 -2
  73. package/dist/lib/daemon/requests.d.ts +4 -0
  74. package/dist/lib/daemon/requests.js +2 -0
  75. package/dist/lib/daemon/run.js +94 -8
  76. package/dist/lib/daemon/supervisor.d.ts +8 -0
  77. package/dist/lib/daemon/supervisor.js +101 -1
  78. package/dist/lib/daemon/sync-runner.d.ts +58 -0
  79. package/dist/lib/daemon/sync-runner.js +277 -0
  80. package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
  81. package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
  82. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  83. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  84. package/dist/lib/flag-registry.d.ts +12 -7
  85. package/dist/lib/flag-registry.js +66 -75
  86. package/dist/lib/flag-snapshot-file.d.ts +20 -0
  87. package/dist/lib/flag-snapshot-file.js +91 -0
  88. package/dist/lib/lanes/claude-balance.d.ts +6 -1
  89. package/dist/lib/lanes/claude-balance.js +46 -8
  90. package/dist/lib/lanes/config.js +15 -0
  91. package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
  92. package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
  93. package/dist/lib/lanes/index.d.ts +1 -1
  94. package/dist/lib/lanes/keep-alive.d.ts +3 -0
  95. package/dist/lib/lanes/keep-alive.js +3 -0
  96. package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
  97. package/dist/lib/lanes/outpost-dispatch.js +11 -2
  98. package/dist/lib/lanes/reconcile.js +20 -4
  99. package/dist/lib/lanes/spawn.js +17 -10
  100. package/dist/lib/lanes/store.js +45 -1
  101. package/dist/lib/lanes/types.d.ts +30 -1
  102. package/dist/lib/lanes/types.js +2 -2
  103. package/dist/lib/lanes/watcher.js +3 -2
  104. package/dist/lib/process-flag-company-context.d.ts +8 -0
  105. package/dist/lib/process-flag-company-context.js +67 -0
  106. package/dist/main.js +3 -1
  107. package/dist/native-fast-route.d.ts +3 -0
  108. package/dist/native-fast-route.js +39 -0
  109. package/dist/secrets-env-fast-path.d.ts +5 -0
  110. package/dist/secrets-env-fast-path.js +16 -0
  111. package/dist/secrets-env-fast-route.d.ts +18 -0
  112. package/dist/secrets-env-fast-route.js +54 -0
  113. package/dist/utils/cli-telemetry.d.ts +26 -2
  114. package/dist/utils/cli-telemetry.js +178 -3
  115. package/dist/utils/hq-root-resolver.d.ts +13 -0
  116. package/dist/utils/hq-root-resolver.js +103 -0
  117. package/dist/utils/scaffold-fast-import-guard.js +6 -1
  118. package/dist/utils/vault-api.d.ts +8 -0
  119. package/dist/utils/vault-api.js +13 -1
  120. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,113 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.277.0] — 2026-09-29
6
+
7
+ ### Fixed
8
+
9
+ - `hq lanes accounts list` and `accounts balance` now report a Claude account
10
+ whose home is missing on the current host and show the `set-home` repair
11
+ command. Balance exits non-zero for an enabled account in that state. On
12
+ Linux, account login stores a host-specific home and requires `--home` when
13
+ an existing account only has a shared home.
14
+ - Claude account balancing can use the default-off
15
+ `lanes.claude-account-balance-signals` hq-flags key to break equal live-lane
16
+ counts by the age of each account's latest completed lane run when provider
17
+ percentage data is unavailable.
18
+
19
+ ### Added
20
+
21
+ - Add a default-off `cli.secrets-env-fast-path` hq-flags route for `hq secrets
22
+ env`. It skips the full CLI graph while reusing the existing credential,
23
+ usage-audit, server authorization, and offline-cache behavior.
24
+ - `hq lanes list --json` accepts repeatable `--lane` filters, `--active`, and
25
+ owner-only hire-hierarchy snapshots. Remote lane lookup now requests the
26
+ needed hierarchy once per Outpost invocation and falls back to an unfiltered
27
+ list when an older owner CLI rejects a new filter. Internal snapshot switches
28
+ stay out of normal help and the generated command catalog.
29
+ - Add the default-off `checkpoint.ontology.dominant-sibling-link` hq-flags key.
30
+ When it is on, checkpoint ontology apply moves a knowledge link from an entity
31
+ whose name another type also holds to the same-name entity that other entity
32
+ files link to most. The move needs at least three times the inbound links and
33
+ at least five. An example is project Build Your Own AGI moving to the concept,
34
+ or the numbered copy `hq-core 2` moving to `hq-core`. Moves are reported
35
+ under `dominant_sibling_redirects`, and no entity file is written.
36
+ - Add the default-off `lanes.running-superseded-message-delivery` hq-flags key
37
+ so messages can reach otherwise messageable superseded lanes outside the done
38
+ and interrupted states. Accepted messages include a warning; done and
39
+ interrupted lanes keep the existing senior and successor checks.
40
+ - `hq core delegate resolve`, `pickup`, `repo`, `secrets`, and `transfer` are
41
+ available through native implementations when the default-off
42
+ `core-native-delegate` flag is enabled; the bundled scripts remain as the
43
+ rollback path.
44
+
45
+ - `hq lanes set <lane> --model <model> --effort <level> [--reason <text>]`
46
+ changes the model or effort a lane uses from its next resume, including the
47
+ keep-alive resume of a done lead. Before, keep-alive always resumed on the
48
+ recorded values, so a lead's model could not change without racing it. The
49
+ lane itself or its senior may run it; each change is kept in
50
+ `settings_history` and shown by `hq lanes show`.
51
+ - `hq daemon sync` reports runner ownership and health, starts an immediate sync pass, pauses or resumes sync, and sets per-company sync mode. The daemon waits for an existing desktop runner to release its lease before starting its own.
52
+ - `hq lanes interrupt <lane-id>` stops a running lane's turn and leaves it
53
+ `interrupted` (reason `operator_interrupt`) with its provider session, so
54
+ `hq lanes resume` continues it. Keep-alive does not resume it. The command
55
+ records the session id from the interrupted turn's output, holds the
56
+ provider's admission lock until the worker has stopped, and reports
57
+ `resumable: false` with a stderr warning when the turn produced no session id.
58
+ - An operator `hq lanes resume` clears keep-alive's `terminal_state` block, so
59
+ keep-alive can continue the lane after that turn.
60
+
61
+ ### Changed
62
+
63
+ - `hq lanes stop` on a running lane warns on stderr that the lane is killed and
64
+ cannot be resumed, names `hq lanes interrupt`, and adds `terminal: true` and
65
+ `resumable: false` to its JSON output, including for Outpost lanes stopped
66
+ from the primary.
67
+ - The lane drain hook avoids loading the lanes module graph when no lane is
68
+ resolved or its drop box is empty, while continuing to record tool activity
69
+ and using the full drain path whenever delivery or replay may be needed.
70
+
71
+ ### Fixed
72
+
73
+ - Doctor command tests use temporary home and state directories and skip the
74
+ live integrations inventory, so their results do not depend on a developer's
75
+ local HQ state.
76
+ - Outpost resume account moves forward the primary's hire-grant context through
77
+ the owner-side session move. The owner checks the current senior and hire
78
+ capacity before starting the worker.
79
+ - Security: `hq dm inbox --json` and agent-side raw message output omit the delegated `actingContext` object.
80
+
81
+ ## [5.276.0] — 2026-09-28
82
+
83
+ ### Added
84
+
85
+ - Suggest inviting a teammate after the billing link in an over-limit Starter
86
+ notice for a one-member company, behind the default-off
87
+ `cli.limit-invite-teammate-v1` flag.
88
+ - Add a default-off native route for `hq core timeout-guard`, backed by a
89
+ private company flag snapshot and a paired CI latency benchmark.
90
+
91
+ ### Changed
92
+
93
+ - Starter setup guidance now says it includes one integration.
94
+
95
+ ### Fixed
96
+
97
+ - `hq core checkpoint` no longer loses the company files of a checkpoint that
98
+ was queued behind a busy sibling, behind the `checkpoint.ontology.queued-touches`
99
+ flag (off by default). The queued checkpoint records its company files, and
100
+ the next sibling run adds them to the ontology plan. A run whose own
101
+ checkpoint names no company takes the ontology step for the company whose
102
+ files have waited longest.
103
+ - `hq feedback bug|feature` validates report content and the serialized request
104
+ size before uploading screenshots or logs, avoiding orphaned attachments when
105
+ the server would reject the submission.
106
+
107
+ - Reconcile clears a stale `superseded` keep-alive block when no active successor
108
+ remains; the five-minute minimum retry delay still applies. The block is
109
+ cleared even when the lead then waits on backoff or a full queue, and the
110
+ clear does not move a done lead's retry clock past its done envelope.
111
+
5
112
  ## [5.275.0] — 2026-09-28
6
113
 
7
114
  ### Fixed
@@ -13,6 +120,9 @@
13
120
 
14
121
  ### Added
15
122
 
123
+ - Track CLI feature events behind the default-off `cli.feature-used-telemetry-v1`
124
+ hq-flags key with a separate one-time disclosure.
125
+
16
126
  - Outpost daemons can take over running services after a CLI update instead
17
127
  of restarting all of them, behind the `daemon.supervisor-handoff` flag (off
18
128
  by default). On Linux, the updated daemon adopts the services the previous
@@ -45,6 +155,8 @@
45
155
  `outpost_self_target`.
46
156
  - `hq sync status` validates an explicit `--hq-root` and continues to read
47
157
  journals from the configured state directory, which it now prints.
158
+ - Root daemon tests use a temporary system adopted-record file; the CLI still
159
+ defaults to `/var/lib/hq/daemon/adopted.json`.
48
160
 
49
161
  ## [5.274.1] — 2026-09-28
50
162
 
package/README.md CHANGED
@@ -47,6 +47,18 @@ plane can accept the event. The body does not include command names, arguments,
47
47
  timings, file contents, or secrets. The guidance fields are sent only when that
48
48
  optional feature is enabled and a person sees or follows its next step.
49
49
 
50
+ When the default-off hq-flags key `cli.feature-used-telemetry-v1` is enabled,
51
+ the CLI sends `cli_feature_used` for supported secret reads (`hq secrets exec`
52
+ and `hq run`) and lane create and resume actions. Each event carries
53
+ `properties.kind` and `properties.outcome` (`success` or `failure`). Events also
54
+ carry the authenticated company UID when available. hq-pro resolves the signed-in
55
+ identity. Events omit command names and arguments, secret names and values,
56
+ prompt text, file paths, and command lines. User hooks and skills run in the
57
+ provider host, which hq-cli cannot see. The default signed-in notice above is
58
+ unchanged. On interactive TTY runs, the first enabled feature event has a
59
+ separate one-time notice naming the feature kind, outcome, and company UID
60
+ when available.
61
+
50
62
  The first signed-in interactive run (stdout is a TTY, and the command is not
51
63
  `--json`) prints this line once on stderr and records that it was shown under
52
64
  `~/.hq/cli-telemetry-disclosed`:
@@ -323,10 +323,10 @@ HQ/
323
323
  HQ has three plans: **Starter**, **HQ Workforce** ($500/month per workspace), and
324
324
  **Enterprise**.
325
325
 
326
- Starter is free. It holds up to 5 members, and it does not include integrations
327
- or MCP, Atlas, or telemetry. Everything else — knowledge, projects, policies,
328
- workers, secrets, vault storage, `hq deploy`, and `hq share` — works the same as
329
- on the paid plans.
326
+ Starter is free. It holds up to 5 members and includes 1 integration. MCP,
327
+ Atlas, and telemetry are not included. Everything else — knowledge, projects,
328
+ policies, workers, secrets, vault storage, `hq deploy`, and `hq share` — works
329
+ the same as on the paid plans.
330
330
 
331
331
  ### When a workspace goes over
332
332
 
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # hq-core: public
3
+ # hq-delegate-pickup.sh — close the delegation loop with a receipt or a FAILED
4
+ # state (2026-09-07).
5
+ #
6
+ # Why: the delegate pipeline ended at "sent", which is a send receipt, not a
7
+ # delivery receipt. Seven delegations sat at sent/building with no way to tell
8
+ # whether the recipient could act. This script adds the two missing states:
9
+ #
10
+ # sent --(evidence)--> picked-up recipient demonstrably has the work
11
+ # sent --(silence past window)--> failed nobody picked it up; sender is told why
12
+ #
13
+ # Evidence, checked in order (any one suffices):
14
+ # 1. --ack "<text>" an explicit acknowledgement (DM reply, chat) —
15
+ # recorded verbatim with a timestamp
16
+ # 2. repo branch has a commit by the recipient after sentAt
17
+ # (manifest.repo.path + repo.branch, author email == to.principal)
18
+ # 3. --evidence "<free text>" any other proof the operator observed
19
+ #
20
+ # Usage:
21
+ # hq-delegate-pickup.sh --manifest <path> --ack "<text>" # record receipt
22
+ # hq-delegate-pickup.sh --manifest <path> --evidence "<text>" # record receipt
23
+ # hq-delegate-pickup.sh --manifest <path> --check [--window-hours N] # probe repo; fail if silent past window (default 72)
24
+ # hq-delegate-pickup.sh --manifest <path> --status # print state line
25
+ #
26
+ # Exit 0: picked-up (or already). Exit 5: failed (window elapsed, no evidence).
27
+ # Exit 6: still waiting (inside window, no evidence). Exit 2: usage/precondition.
28
+ set -uo pipefail
29
+ MANIFEST=""; ACK=""; EVIDENCE=""; CHECK=0; STATUS_ONLY=0; WINDOW="${HQ_DELEGATE_PICKUP_WINDOW_HOURS:-72}"
30
+ while [ $# -gt 0 ]; do
31
+ case "$1" in
32
+ --manifest) MANIFEST="$2"; shift 2 ;;
33
+ --ack) ACK="$2"; shift 2 ;;
34
+ --evidence) EVIDENCE="$2"; shift 2 ;;
35
+ --check) CHECK=1; shift ;;
36
+ --window-hours) WINDOW="$2"; shift 2 ;;
37
+ --status) STATUS_ONLY=1; shift ;;
38
+ -h|--help) sed -n 2,28p "$0"; exit 0 ;;
39
+ *) echo "hq-delegate-pickup: unknown arg $1" >&2; exit 2 ;;
40
+ esac
41
+ done
42
+ [ -n "$MANIFEST" ] && [ -r "$MANIFEST" ] || { echo "usage: --manifest <path> (--ack|--evidence|--check|--status)" >&2; exit 2; }
43
+ command -v jq >/dev/null 2>&1 || { echo "hq-delegate-pickup: jq required" >&2; exit 2; }
44
+ NOW="${HQ_DELEGATE_NOW:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}"
45
+ STATUS="$(jq -r '.status // empty' "$MANIFEST")"
46
+ SENT_AT="$(jq -r '.sentAt // empty' "$MANIFEST")"
47
+ TO="$(jq -r '.to.principal // empty' "$MANIFEST")"
48
+ NAME="$(jq -r '.to.displayName // .to.principal // "the recipient"' "$MANIFEST")"
49
+
50
+ if [ "$STATUS_ONLY" = 1 ]; then
51
+ jq -r '"\(.delegationId) \(.status)" + (if .pickedUpAt then " picked-up " + .pickedUpAt + " via " + (.pickup.kind // "?") else "" end) + (if .failedAt then " failed " + .failedAt + ": " + (.failure.reason // "") else "" end)' "$MANIFEST"
52
+ exit 0
53
+ fi
54
+ case "$STATUS" in
55
+ picked-up) echo "hq-delegate-pickup: already picked up ($(jq -r '.pickedUpAt' "$MANIFEST"))"; exit 0 ;;
56
+ failed) echo "hq-delegate-pickup: already failed ($(jq -r '.failure.reason // ""' "$MANIFEST"))"; exit 5 ;;
57
+ sent) ;;
58
+ *) echo "hq-delegate-pickup: manifest status is '$STATUS' — only a 'sent' delegation can be picked up (send it first)" >&2; exit 2 ;;
59
+ esac
60
+
61
+ record_pickup() { # <kind> <detail>
62
+ jq --arg now "$NOW" --arg k "$1" --arg d "$2" \
63
+ '.status = "picked-up" | .pickedUpAt = $now | .pickup = {kind: $k, detail: $d, recordedAt: $now}' \
64
+ "$MANIFEST" > "$MANIFEST.tmp" && mv "$MANIFEST.tmp" "$MANIFEST"
65
+ echo "hq-delegate-pickup: $NAME picked up the delegation ($1) — status advanced to 'picked-up'"
66
+ exit 0
67
+ }
68
+ [ -n "$ACK" ] && record_pickup ack "$ACK"
69
+ [ -n "$EVIDENCE" ] && record_pickup evidence "$EVIDENCE"
70
+
71
+ [ "$CHECK" = 1 ] || { echo "hq-delegate-pickup: nothing to do (pass --ack, --evidence, --check, or --status)" >&2; exit 2; }
72
+
73
+ # Evidence 2: a commit by the recipient on the handed-over branch after sentAt.
74
+ REPO="$(jq -r '.repo.path // empty' "$MANIFEST")"
75
+ BRANCH="$(jq -r '.repo.branch // empty' "$MANIFEST")"
76
+ if [ -n "$REPO" ] && [ -d "$REPO" ] && [ -n "$BRANCH" ] && [ -n "$TO" ]; then
77
+ git -C "$REPO" fetch -q origin "$BRANCH" 2>/dev/null || true
78
+ ref="origin/$BRANCH"; git -C "$REPO" show-ref --verify --quiet "refs/remotes/$ref" || ref="$BRANCH"
79
+ c="$(git -C "$REPO" log "$ref" --author="$TO" ${SENT_AT:+--since="$SENT_AT"} --format='%h %ad %s' --date=short -1 2>/dev/null || true)"
80
+ [ -n "$c" ] && record_pickup commit "$c on $BRANCH"
81
+ fi
82
+
83
+ # No evidence. Inside the window: waiting. Past it: FAILED, with the reason.
84
+ if [ -n "$SENT_AT" ]; then
85
+ sent_epoch="$(date -u -d "$SENT_AT" +%s 2>/dev/null || date -u -j -f %Y-%m-%dT%H:%M:%SZ "$SENT_AT" +%s 2>/dev/null || echo 0)"
86
+ now_epoch="$(date -u -d "$NOW" +%s 2>/dev/null || date -u -j -f %Y-%m-%dT%H:%M:%SZ "$NOW" +%s 2>/dev/null || date +%s)"
87
+ age_h=$(( (now_epoch - sent_epoch) / 3600 ))
88
+ else
89
+ age_h=0
90
+ fi
91
+ if [ "$age_h" -lt "$WINDOW" ]; then
92
+ echo "hq-delegate-pickup: waiting — no pickup evidence from $NAME yet (${age_h}h of a ${WINDOW}h window)"
93
+ exit 6
94
+ fi
95
+ reason="no pickup evidence from $NAME within ${WINDOW}h of send (${SENT_AT:-unknown}): no acknowledgement, no commit by $TO on ${BRANCH:-the branch}"
96
+ jq --arg now "$NOW" --arg r "$reason" '.status = "failed" | .failedAt = $now | .failure = {reason: $r, recordedAt: $now}' \
97
+ "$MANIFEST" > "$MANIFEST.tmp" && mv "$MANIFEST.tmp" "$MANIFEST"
98
+ echo "hq-delegate-pickup: FAILED — $reason. Re-send with a direct ask, or hand it to someone else." >&2
99
+ exit 5
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env bash
2
+ # hq-core: public
3
+ # hq-delegate-repo.sh — code/branch handover for a delegation: make sure the
4
+ # project's branch exists on the remote, record uncommitted work as NOT
5
+ # transferred, and verify the recipient's repository access read-only.
6
+ #
7
+ # Usage:
8
+ # core/scripts/hq-delegate-repo.sh --manifest <path> [--yes] [--github-user <login>]
9
+ #
10
+ # Behavior (driven by the manifest's repo{} block; no-op when repo is null):
11
+ # - records remote URL and the branch's headSha into the manifest
12
+ # - a branch that exists only locally requires confirmation: without --yes
13
+ # the helper prints the plan and exits 2; with --yes it pushes via an
14
+ # explicitly anchored `git -C <abs path> push -u origin <branch>`
15
+ # - dirty/untracked files are captured into repo.dirtyFiles[] and appended
16
+ # to BRIEF.md as work NOT transferred — never silently dropped
17
+ # - recipient access is checked read-only via gh when --github-user is
18
+ # given; otherwise (or on a gh failure) repo.accessVerified stays false
19
+ # and the brief says so plainly — the helper never claims unverified access
20
+ # - never touches vaultPrefixes[] and never pushes repo content to the vault
21
+ #
22
+ # Env: HQ_ROOT — HQ root override (default: resolved from script location)
23
+
24
+ set -euo pipefail
25
+
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
28
+
29
+ usage() { sed -n '3,22p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
30
+ die() { echo "hq-delegate-repo: $*" >&2; exit 1; }
31
+
32
+ command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
33
+
34
+ MANIFEST="" YES=0 GH_USER=""
35
+ while [ $# -gt 0 ]; do
36
+ case "$1" in
37
+ --manifest) MANIFEST="${2:-}"; shift 2 ;;
38
+ --yes) YES=1; shift ;;
39
+ --github-user) GH_USER="${2:-}"; shift 2 ;;
40
+ -h|--help) usage; exit 0 ;;
41
+ *) die "unknown argument: $1" ;;
42
+ esac
43
+ done
44
+
45
+ [ -n "$MANIFEST" ] || die "--manifest is required"
46
+ [ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST"
47
+ jq -e . "$MANIFEST" >/dev/null 2>&1 || die "manifest is not valid JSON: $MANIFEST"
48
+
49
+ BUNDLE_DIR="$(cd "$(dirname "$MANIFEST")" && pwd)"
50
+ BRIEF="$BUNDLE_DIR/BRIEF.md"
51
+
52
+ # --- no-repo projects skip cleanly -------------------------------------------
53
+
54
+ if jq -e '.repo == null' "$MANIFEST" >/dev/null; then
55
+ echo "hq-delegate-repo: project has no repoPath — nothing to hand over"
56
+ exit 0
57
+ fi
58
+
59
+ REPO_REL="$(jq -r '.repo.path // empty' "$MANIFEST")"
60
+ BRANCH="$(jq -r '.repo.branch // empty' "$MANIFEST")"
61
+ [ -n "$REPO_REL" ] || die "manifest repo block has no path"
62
+
63
+ REPO_DIR="$HQ_ROOT/$REPO_REL"
64
+ [ -d "$REPO_DIR/.git" ] || git -C "$REPO_DIR" rev-parse --git-dir >/dev/null 2>&1 \
65
+ || die "not a git repository: $REPO_DIR"
66
+
67
+ REMOTE_URL="$(git -C "$REPO_DIR" remote get-url origin 2>/dev/null || true)"
68
+
69
+ # --- dirty / untracked work is recorded, never dropped -----------------------
70
+
71
+ DIRTY_JSON="$(git -C "$REPO_DIR" status --porcelain | sed 's/^...//' | jq -R . | jq -cs .)"
72
+
73
+ # --- branch state -------------------------------------------------------------
74
+
75
+ BRANCH_PUSHED=false HEAD_SHA=""
76
+ if [ -n "$BRANCH" ]; then
77
+ git check-ref-format --branch "$BRANCH" >/dev/null 2>&1 || die "invalid branch name in PRD: $BRANCH"
78
+ LOCAL_SHA="$(git -C "$REPO_DIR" rev-parse --verify --quiet "refs/heads/$BRANCH" || true)"
79
+ REMOTE_SHA="$(git -C "$REPO_DIR" rev-parse --verify --quiet "refs/remotes/origin/$BRANCH" || true)"
80
+
81
+ if [ -n "$REMOTE_SHA" ]; then
82
+ BRANCH_PUSHED=true
83
+ HEAD_SHA="$REMOTE_SHA"
84
+ elif [ -n "$LOCAL_SHA" ]; then
85
+ echo "Branch handover plan — repo '$REPO_REL':"
86
+ echo " branch '$BRANCH' exists only locally ($LOCAL_SHA)"
87
+ echo " action: git -C $REPO_DIR push -u origin $BRANCH"
88
+ if [ "$YES" -ne 1 ]; then
89
+ echo
90
+ echo "hq-delegate-repo: confirmation required — re-run with --yes to push the branch" >&2
91
+ exit 2
92
+ fi
93
+ git -C "$REPO_DIR" push -u origin "$BRANCH" \
94
+ || die "failed to push branch '$BRANCH' to origin"
95
+ BRANCH_PUSHED=true
96
+ HEAD_SHA="$LOCAL_SHA"
97
+ else
98
+ echo "hq-delegate-repo: branch '$BRANCH' does not exist yet (declared in the PRD, not started) — recording as unpushed"
99
+ fi
100
+ fi
101
+
102
+ # --- read-only recipient access check ----------------------------------------
103
+
104
+ ACCESS_VERIFIED=false ACCESS_NOTE=""
105
+ if [ -n "$GH_USER" ] && [ -n "$REMOTE_URL" ] && command -v gh >/dev/null 2>&1; then
106
+ # owner/repo from ssh or https remote forms
107
+ OWNER_REPO="$(printf '%s' "$REMOTE_URL" | sed -E 's#^(git@[^:]+:|https?://[^/]+/)##; s#\.git$##')"
108
+ if gh api "repos/$OWNER_REPO/collaborators/$GH_USER" >/dev/null 2>&1; then
109
+ ACCESS_VERIFIED=true
110
+ ACCESS_NOTE="verified: $GH_USER is a collaborator on $OWNER_REPO"
111
+ else
112
+ ACCESS_NOTE="UNVERIFIED: could not confirm $GH_USER has access to $OWNER_REPO — the recipient may need to be invited before they can pull the branch"
113
+ fi
114
+ else
115
+ ACCESS_NOTE="UNVERIFIED: recipient repository access was not checked (no GitHub login supplied) — confirm they can reach the repo before assuming so"
116
+ fi
117
+
118
+ # --- manifest update (repo block only — vaultPrefixes are never touched) -----
119
+
120
+ TMP_MANIFEST="$(mktemp)"
121
+ jq \
122
+ --arg remote "$REMOTE_URL" \
123
+ --arg headSha "$HEAD_SHA" \
124
+ --argjson dirty "$DIRTY_JSON" \
125
+ --argjson pushed "$BRANCH_PUSHED" \
126
+ --argjson access "$ACCESS_VERIFIED" \
127
+ --arg accessNote "$ACCESS_NOTE" \
128
+ '.repo.remote = (if $remote == "" then null else $remote end)
129
+ | .repo.headSha = (if $headSha == "" then null else $headSha end)
130
+ | .repo.dirtyFiles = $dirty
131
+ | .repo.branchPushed = $pushed
132
+ | .repo.accessVerified = $access
133
+ | .repo.accessNote = $accessNote' \
134
+ "$MANIFEST" > "$TMP_MANIFEST" && mv "$TMP_MANIFEST" "$MANIFEST"
135
+
136
+ # --- brief: call out untransferred work and unverified access ----------------
137
+
138
+ if [ -f "$BRIEF" ]; then
139
+ DIRTY_COUNT="$(printf '%s' "$DIRTY_JSON" | jq 'length')"
140
+ if [ "$DIRTY_COUNT" -gt 0 ] || [ "$ACCESS_VERIFIED" != "true" ]; then
141
+ {
142
+ echo
143
+ echo "## Code handover notes"
144
+ echo
145
+ if [ "$DIRTY_COUNT" -gt 0 ]; then
146
+ echo "The delegator's working tree had uncommitted changes that are **not transferred** with this delegation. If something seems missing, these files are why — ask the delegator to commit and push them:"
147
+ echo
148
+ printf '%s' "$DIRTY_JSON" | jq -r '.[] | "- `\(.)`"'
149
+ echo
150
+ fi
151
+ if [ "$ACCESS_VERIFIED" != "true" ]; then
152
+ echo "$ACCESS_NOTE"
153
+ fi
154
+ } >> "$BRIEF"
155
+ fi
156
+ fi
157
+
158
+ echo "hq-delegate-repo: recorded branch state (pushed=$BRANCH_PUSHED, dirty=$(printf '%s' "$DIRTY_JSON" | jq 'length') files, accessVerified=$ACCESS_VERIFIED)"
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env bash
2
+ # hq-core: public
3
+ # hq-delegate-resolve.sh — resolve a delegation recipient to a confirmed
4
+ # principal (person email / prs_ uid, or fleet-agent agt_ uid). Read-only.
5
+ #
6
+ # Usage:
7
+ # core/scripts/hq-delegate-resolve.sh --company <slug> --to <token>
8
+ #
9
+ # Output (stdout, JSON): {kind, principal, displayName, source}
10
+ # kind — "person" | "agent"
11
+ # principal — confirmed email, prs_… or agt_… uid
12
+ # source — "verbatim" | "people-resolve" | "agents-list"
13
+ #
14
+ # Exit codes:
15
+ # 0 — resolved
16
+ # 1 — usage error (missing/invalid arguments)
17
+ # 3 — ambiguous: candidate list printed to stdout as JSON
18
+ # {status:"ambiguous", matches:[…]} — caller must present a picker
19
+ # 4 — not found (or found with no email): plain message on stderr
20
+ #
21
+ # Tenancy: every lookup is scoped with an explicit --company. This helper
22
+ # never enumerates or falls back across companies — single-company by
23
+ # construction, matching the /dm skill's resolution contract.
24
+ #
25
+ # Fast path: a token containing "@" or starting with prs_ / agt_ passes
26
+ # through verbatim with NO lookup, preserving existing invocations exactly.
27
+
28
+ set -euo pipefail
29
+
30
+ usage() {
31
+ sed -n '3,26p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
32
+ }
33
+
34
+ die_usage() {
35
+ echo "hq-delegate-resolve: $*" >&2
36
+ usage >&2
37
+ exit 1
38
+ }
39
+
40
+ command -v jq >/dev/null 2>&1 || { echo "hq-delegate-resolve: jq is required" >&2; exit 1; }
41
+
42
+ COMPANY="" TOKEN=""
43
+ while [ $# -gt 0 ]; do
44
+ case "$1" in
45
+ --company) COMPANY="${2:-}"; shift 2 ;;
46
+ --to) TOKEN="${2:-}"; shift 2 ;;
47
+ -h|--help) usage; exit 0 ;;
48
+ *) die_usage "unknown argument: $1" ;;
49
+ esac
50
+ done
51
+
52
+ [ -n "$COMPANY" ] || die_usage "--company is required (single-company lookups only; never a multi-company scan)"
53
+ [ -n "$TOKEN" ] || die_usage "--to is required"
54
+
55
+ emit() { # kind principal displayName source
56
+ jq -cn --arg kind "$1" --arg principal "$2" --arg name "$3" --arg source "$4" \
57
+ '{kind: $kind, principal: $principal,
58
+ displayName: (if $name == "" then null else $name end), source: $source}'
59
+ }
60
+
61
+ # Strip CLI noise (version warnings, session-refresh notices) before the JSON
62
+ # payload: keep everything from the first line that starts a JSON value.
63
+ json_only() {
64
+ awk 'started {print; next} /^[[:space:]]*[{[]/ {started=1; print}'
65
+ }
66
+
67
+ # --- fast path: already a principal — pass through verbatim, no lookup -------
68
+
69
+ case "$TOKEN" in
70
+ agt_*)
71
+ emit agent "$TOKEN" "" verbatim
72
+ exit 0
73
+ ;;
74
+ prs_*|*@*)
75
+ emit person "$TOKEN" "" verbatim
76
+ exit 0
77
+ ;;
78
+ esac
79
+
80
+ # --- name → people roster (single-company, read-only) ------------------------
81
+
82
+ PEOPLE_RAW="$(hq people resolve "$TOKEN" --json --company "$COMPANY" 2>/dev/null | json_only || true)"
83
+ PEOPLE_STATUS=""
84
+ if [ -n "$PEOPLE_RAW" ] && printf '%s' "$PEOPLE_RAW" | jq -e . >/dev/null 2>&1; then
85
+ PEOPLE_STATUS="$(printf '%s' "$PEOPLE_RAW" | jq -r '.status // empty')"
86
+ fi
87
+
88
+ case "$PEOPLE_STATUS" in
89
+ found)
90
+ EMAIL="$(printf '%s' "$PEOPLE_RAW" | jq -r '.email // empty')"
91
+ NAME="$(printf '%s' "$PEOPLE_RAW" | jq -r '.name // .displayName // empty')"
92
+ [ -n "$EMAIL" ] || { echo "hq-delegate-resolve: resolver returned found but no email for '$TOKEN'" >&2; exit 4; }
93
+ emit person "$EMAIL" "$NAME" people-resolve
94
+ exit 0
95
+ ;;
96
+ ambiguous)
97
+ printf '%s' "$PEOPLE_RAW" | jq -c '{status: "ambiguous", matches: (.matches // [])}'
98
+ exit 3
99
+ ;;
100
+ no_email)
101
+ # A person entry without an email is often a fleet agent's roster row —
102
+ # fall through to the agent roster before treating this as terminal.
103
+ PERSON_NO_EMAIL=1
104
+ ;;
105
+ not_found|"")
106
+ : # fall through to the fleet-agent roster
107
+ ;;
108
+ *)
109
+ echo "hq-delegate-resolve: unexpected resolver status '$PEOPLE_STATUS' for '$TOKEN'" >&2
110
+ exit 4
111
+ ;;
112
+ esac
113
+
114
+ # --- name → fleet-agent roster (same company, read-only) ---------------------
115
+
116
+ AGENTS_RAW="$(hq agents list --company "$COMPANY" --json 2>/dev/null | json_only || true)"
117
+ if [ -z "$AGENTS_RAW" ] || ! printf '%s' "$AGENTS_RAW" | jq -e . >/dev/null 2>&1; then
118
+ AGENTS_RAW="[]"
119
+ fi
120
+
121
+ MATCHES="$(printf '%s' "$AGENTS_RAW" | jq -c --arg t "$TOKEN" '
122
+ [ .[]
123
+ | select((.name // .displayName // "") | ascii_downcase == ($t | ascii_downcase))
124
+ | {agentUid: (.agentUid // .uid // empty), name: (.name // .displayName // "")}
125
+ | select(.agentUid != "")
126
+ ]')"
127
+ MATCH_COUNT="$(printf '%s' "$MATCHES" | jq 'length')"
128
+
129
+ if [ "$MATCH_COUNT" -eq 1 ]; then
130
+ emit agent \
131
+ "$(printf '%s' "$MATCHES" | jq -r '.[0].agentUid')" \
132
+ "$(printf '%s' "$MATCHES" | jq -r '.[0].name')" \
133
+ agents-list
134
+ exit 0
135
+ elif [ "$MATCH_COUNT" -gt 1 ]; then
136
+ printf '%s' "$MATCHES" | jq -c '{status: "ambiguous", matches: .}'
137
+ exit 3
138
+ fi
139
+
140
+ if [ "${PERSON_NO_EMAIL:-0}" -eq 1 ]; then
141
+ echo "hq-delegate-resolve: '$TOKEN' was found in company '$COMPANY' but has no email on record and matches no fleet agent — pass their exact email, personUid (prs_…), or agentUid (agt_…) instead" >&2
142
+ else
143
+ echo "hq-delegate-resolve: no teammate or fleet agent named '$TOKEN' found in company '$COMPANY' — pass the exact email, personUid (prs_…), or agentUid (agt_…) instead" >&2
144
+ fi
145
+ exit 4