@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.
- package/CHANGELOG.md +112 -0
- package/README.md +12 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +4 -4
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-checkpoint.d.ts +4 -2
- package/dist/commands/core-checkpoint.js +34 -3
- package/dist/commands/core-command-catalog.d.ts +4 -2
- package/dist/commands/core-command-catalog.js +5 -2
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core-ontology.js +3 -3
- package/dist/commands/core.js +11 -1
- package/dist/commands/daemon.d.ts +4 -0
- package/dist/commands/daemon.js +175 -2
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/feedback.d.ts +6 -0
- package/dist/commands/feedback.js +86 -37
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +402 -49
- package/dist/commands/run-feature-telemetry.d.ts +3 -0
- package/dist/commands/run-feature-telemetry.js +6 -0
- package/dist/commands/run.js +3 -2
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +80 -45
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/commands/whoami.d.ts +4 -6
- package/dist/commands/whoami.js +39 -8
- package/dist/fast-core.d.ts +6 -7
- package/dist/fast-core.js +22 -8
- package/dist/fast-scaffold.d.ts +33 -7
- package/dist/fast-scaffold.js +104 -8
- package/dist/index.js +32 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/billing/plan-lock.d.ts +3 -1
- package/dist/lib/billing/plan-lock.js +7 -1
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +60 -3
- package/dist/lib/checkpoint-ontology/company.d.ts +8 -4
- package/dist/lib/checkpoint-ontology/company.js +15 -7
- package/dist/lib/checkpoint-ontology/plan.d.ts +36 -0
- package/dist/lib/checkpoint-ontology/plan.js +66 -4
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +32 -0
- package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
- package/dist/lib/core-utils/timeout-guard-native.js +40 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
- package/dist/lib/core-utils/timeout-guard.js +28 -13
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.d.ts +12 -7
- package/dist/lib/flag-registry.js +66 -75
- package/dist/lib/flag-snapshot-file.d.ts +20 -0
- package/dist/lib/flag-snapshot-file.js +91 -0
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.js +15 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/reconcile.js +20 -4
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -2
- package/dist/lib/process-flag-company-context.d.ts +8 -0
- package/dist/lib/process-flag-company-context.js +67 -0
- package/dist/main.js +3 -1
- package/dist/native-fast-route.d.ts +3 -0
- package/dist/native-fast-route.js +39 -0
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/dist/utils/cli-telemetry.d.ts +26 -2
- package/dist/utils/cli-telemetry.js +178 -3
- package/dist/utils/hq-root-resolver.d.ts +13 -0
- package/dist/utils/hq-root-resolver.js +103 -0
- package/dist/utils/scaffold-fast-import-guard.js +6 -1
- package/dist/utils/vault-api.d.ts +8 -0
- package/dist/utils/vault-api.js +13 -1
- 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
|
|
327
|
-
|
|
328
|
-
workers, secrets, vault storage, `hq deploy`, and `hq share` — works
|
|
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
|