@etiquekit/etq 0.0.1 → 1.0.4
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/AGENTS.md +28 -0
- package/QuickStart.md +86 -0
- package/README.md +77 -9
- package/bin/etiquette +1150 -0
- package/bin/etiquette-core +313 -0
- package/docs/ARCHITECTURE.md +100 -0
- package/docs/CODEX_CLIENT_COMPATIBILITY.md +83 -0
- package/docs/CONCEPTS.md +128 -0
- package/docs/CONCEPT_STATUS.md +67 -0
- package/docs/CORE_PROFILE.md +70 -0
- package/docs/LANE_PROVISIONING.md +157 -0
- package/docs/README.md +29 -0
- package/docs/RELEASE_SURFACE_AUDIT.md +156 -0
- package/docs/SEAT_DISCIPLINE.md +116 -0
- package/docs/SEAT_PROVISIONING.md +100 -0
- package/docs/TEAM_HANDOFF.md +190 -0
- package/docs/WORKTREE_QOL.md +92 -0
- package/docs/contracts/ledger-entry/README.md +83 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.1.md +116 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +90 -0
- package/docs/contracts/ledger-entry/ledger-entry.v0.md +123 -0
- package/docs/contracts/ledger-entry/ledger-query-cli.v0.1.md +64 -0
- package/package.json +42 -11
- package/scripts/install.sh +144 -0
- package/scripts/uninstall.sh +58 -0
- package/templates/etiquette-vanilla-v0/README.md +69 -0
- package/templates/etiquette-vanilla-v0/bin/stamp-vanilla +33 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/.llm-substrate/HANDOFF.md +22 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/README.md +16 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/http.sh +121 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/sanitize.sh +191 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/_lib/state.sh +67 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/compact-local-events +71 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/post-tool-use +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-end +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/session-start +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/stop +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/hooks/user-prompt-submit +64 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/local-event +53 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-receipt +143 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/post-telemetry +112 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-consume-pass +115 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +20 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-init +86 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-post-readiness +171 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/bin/session-init +132 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/ledger/LEDGER.md +7 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/playbacks/PLAYBACK_TEMPLATE.md +31 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/receipts/.gitkeep +1 -0
- package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/tasks/CONTROL-001.yaml +13 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/README.md +6 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/bin/kernel-doctor +11 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/playback.v0.md +37 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/receipt.v0.md +25 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/seat-readiness.v0.md +22 -0
- package/templates/etiquette-vanilla-v0/source/etiquette-kernel/contracts/task.v0.md +18 -0
- package/templates/etiquette-vanilla-v0/validate-vanilla.sh +43 -0
- package/templates/github-actions/README.md +30 -0
- package/templates/github-actions/w1-webhook-wake-canary.yml +111 -0
- package/templates/hosted-receiver/README.md +41 -0
- package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
- package/templates/seat-packs-v0/README.md +72 -0
- package/templates/seat-packs-v0/bin/provision-seat +70 -0
- package/templates/seat-packs-v0/source/claude-code-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/claude-code-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/claude-code-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/codex-seat/.llm-substrate/HANDOFF.md +25 -0
- package/templates/seat-packs-v0/source/codex-seat/README.md +22 -0
- package/templates/seat-packs-v0/source/codex-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/gemini-seat/.llm-substrate/HANDOFF.md +23 -0
- package/templates/seat-packs-v0/source/gemini-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/gemini-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/ollama-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/ollama-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/ollama-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/source/openrouter-seat/.llm-substrate/HANDOFF.md +24 -0
- package/templates/seat-packs-v0/source/openrouter-seat/README.md +19 -0
- package/templates/seat-packs-v0/source/openrouter-seat/bin/seat-doctor +8 -0
- package/templates/seat-packs-v0/validate-seat-packs.sh +27 -0
- package/bin/etq +0 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
|
7
|
+
echo "usage: sh bin/compact-local-events <task-id> [success|partial|blocked|failed]" >&2
|
|
8
|
+
exit 2
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
task_id=$1
|
|
12
|
+
status=${2:-success}
|
|
13
|
+
operator_principal=${ETIQUETTE_OPERATOR_PRINCIPAL:-local-operator}
|
|
14
|
+
execution_seat=${ETIQUETTE_EXECUTION_SEAT:-control-seat}
|
|
15
|
+
event_log=${ETIQUETTE_EVENT_LOG:-"$ROOT/.etiquette/events/local.ndjson"}
|
|
16
|
+
receipt_dir=${ETIQUETTE_RECEIPT_DIR:-"$ROOT/docs/work/receipts"}
|
|
17
|
+
|
|
18
|
+
case "$status" in
|
|
19
|
+
success|partial|blocked|failed) ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "invalid receipt status: $status" >&2
|
|
22
|
+
exit 2
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
if [ ! -f "$event_log" ]; then
|
|
27
|
+
echo "missing local event log: $event_log" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
task_pattern='"task_id":"'"$task_id"'"'
|
|
32
|
+
matches=$(grep -F "$task_pattern" "$event_log" || true)
|
|
33
|
+
|
|
34
|
+
if [ -z "$matches" ]; then
|
|
35
|
+
echo "no events found for task: $task_id" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
event_count=$(printf '%s\n' "$matches" | sed '/^$/d' | wc -l | tr -d ' ')
|
|
40
|
+
first_seq=$(printf '%s\n' "$matches" | sed -n '1s/.*"seq":\([0-9][0-9]*\).*/\1/p')
|
|
41
|
+
last_seq=$(printf '%s\n' "$matches" | sed -n '$s/.*"seq":\([0-9][0-9]*\).*/\1/p')
|
|
42
|
+
last_action=$(printf '%s\n' "$matches" | sed -n '$s/.*"action":"\([^"]*\)".*/\1/p')
|
|
43
|
+
|
|
44
|
+
mkdir -p "$receipt_dir"
|
|
45
|
+
receipt="$receipt_dir/$task_id-receipt.yaml"
|
|
46
|
+
tmp="$receipt.tmp.$$"
|
|
47
|
+
|
|
48
|
+
cat > "$tmp" <<EOF
|
|
49
|
+
task_id: $task_id
|
|
50
|
+
operator_principal: $operator_principal
|
|
51
|
+
execution_seat: $execution_seat
|
|
52
|
+
status: $status
|
|
53
|
+
sources_read:
|
|
54
|
+
- .etiquette/events/local.ndjson
|
|
55
|
+
files_changed:
|
|
56
|
+
- docs/work/receipts/$task_id-receipt.yaml
|
|
57
|
+
validation:
|
|
58
|
+
- sh bin/seat-doctor
|
|
59
|
+
risks: []
|
|
60
|
+
next_owner: control-seat
|
|
61
|
+
compaction:
|
|
62
|
+
scope: local-only
|
|
63
|
+
event_log: .etiquette/events/local.ndjson
|
|
64
|
+
event_count: $event_count
|
|
65
|
+
first_seq: $first_seq
|
|
66
|
+
last_seq: $last_seq
|
|
67
|
+
last_action: $last_action
|
|
68
|
+
EOF
|
|
69
|
+
|
|
70
|
+
mv "$tmp" "$receipt"
|
|
71
|
+
printf 'receipt: %s\n' "$receipt"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/post-tool-use — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook post_tool_use`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook post_tool_use --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook post_tool_use --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/session-end — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook session_end`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook session_end --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook session_end --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/session-start — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook session_start`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook session_start --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook session_start --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/stop — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook stop`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook stop --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook stop --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# bin/hooks/user-prompt-submit — Etiquette Hook Adapter v0.1
|
|
3
|
+
# Maps runtime lifecycle event to etiquette `hook user_prompt_submit`.
|
|
4
|
+
#
|
|
5
|
+
# DISABLED BY DEFAULT. Enable with:
|
|
6
|
+
# export ETIQUETTE_HOOKS_ENABLED=1
|
|
7
|
+
# export ETIQUETTE_WORKSPACE_ID=<opaque-workspace-id>
|
|
8
|
+
# export ETIQUETTE_CLI_SCRIPT=/path/to/etiquette/packages/control/src/cli.ts
|
|
9
|
+
#
|
|
10
|
+
# Optional input:
|
|
11
|
+
# stdin or $ETIQUETTE_HOOK_PAYLOAD = JSON object payload (sanitized server-side)
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - disabled-by-default: silent exit 0 when not opted in
|
|
15
|
+
# - fast-fail: any error is swallowed; never blocks the operator
|
|
16
|
+
# - non-authoritative: writes per-workspace outbox only; no ledger writes
|
|
17
|
+
set -eu
|
|
18
|
+
|
|
19
|
+
if [ "${ETIQUETTE_HOOKS_ENABLED:-0}" != "1" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
CLI_BIN=${ETIQUETTE_CLI_BIN:-bun}
|
|
24
|
+
CLI_SCRIPT=${ETIQUETTE_CLI_SCRIPT:-}
|
|
25
|
+
|
|
26
|
+
if [ -z "$CLI_SCRIPT" ] || [ ! -f "$CLI_SCRIPT" ]; then
|
|
27
|
+
# hook enabled but no cli script located; non-blocking exit
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Payload via env var (preferred) or stdin
|
|
32
|
+
if [ -n "${ETIQUETTE_HOOK_PAYLOAD:-}" ]; then
|
|
33
|
+
payload=$ETIQUETTE_HOOK_PAYLOAD
|
|
34
|
+
elif [ ! -t 0 ]; then
|
|
35
|
+
payload=$(cat)
|
|
36
|
+
else
|
|
37
|
+
payload='{}'
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -n "${ETIQUETTE_WORKSPACE_ID:-}" ]; then
|
|
41
|
+
set -- hook user_prompt_submit --workspace "$ETIQUETTE_WORKSPACE_ID" --payload "$payload"
|
|
42
|
+
else
|
|
43
|
+
set -- hook user_prompt_submit --payload "$payload"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
timeout_seconds=${ETIQUETTE_HOOK_TIMEOUT_SECONDS:-2}
|
|
47
|
+
case "$timeout_seconds" in
|
|
48
|
+
''|*[!0-9]*) timeout_seconds=2 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Run with timeout safety. Output goes to /dev/null so a hook never pollutes the
|
|
52
|
+
# runtime's stdout, and timeout/failure never blocks the operator.
|
|
53
|
+
(
|
|
54
|
+
"$CLI_BIN" "$CLI_SCRIPT" "$@" >/dev/null 2>&1
|
|
55
|
+
) &
|
|
56
|
+
hook_pid=$!
|
|
57
|
+
(
|
|
58
|
+
sleep "$timeout_seconds"
|
|
59
|
+
kill "$hook_pid" 2>/dev/null || true
|
|
60
|
+
) &
|
|
61
|
+
watchdog_pid=$!
|
|
62
|
+
wait "$hook_pid" 2>/dev/null || true
|
|
63
|
+
kill "$watchdog_pid" 2>/dev/null || true
|
|
64
|
+
exit 0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
if [ "$#" -lt 4 ]; then
|
|
7
|
+
echo "usage: sh bin/local-event <task-id> <actor> <action> <message...>" >&2
|
|
8
|
+
exit 2
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
task_id=$1
|
|
12
|
+
actor=$2
|
|
13
|
+
action=$3
|
|
14
|
+
shift 3
|
|
15
|
+
message=$*
|
|
16
|
+
|
|
17
|
+
event_log=${ETIQUETTE_EVENT_LOG:-"$ROOT/.etiquette/events/local.ndjson"}
|
|
18
|
+
event_dir=$(dirname -- "$event_log")
|
|
19
|
+
mkdir -p "$event_dir"
|
|
20
|
+
|
|
21
|
+
lock_dir="$event_dir/.local-event.lock"
|
|
22
|
+
lock_attempts=0
|
|
23
|
+
while ! mkdir "$lock_dir" 2>/dev/null; do
|
|
24
|
+
lock_attempts=$((lock_attempts + 1))
|
|
25
|
+
if [ "$lock_attempts" -ge 100 ]; then
|
|
26
|
+
echo "timed out waiting for local event lock: $lock_dir" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
sleep 0.05
|
|
30
|
+
done
|
|
31
|
+
trap 'rmdir "$lock_dir" 2>/dev/null || true' EXIT INT TERM
|
|
32
|
+
|
|
33
|
+
if [ -f "$event_log" ]; then
|
|
34
|
+
seq=$(wc -l < "$event_log" | tr -d ' ')
|
|
35
|
+
else
|
|
36
|
+
seq=0
|
|
37
|
+
fi
|
|
38
|
+
seq=$((seq + 1))
|
|
39
|
+
at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
40
|
+
|
|
41
|
+
json_escape() {
|
|
42
|
+
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
task_id_json=$(json_escape "$task_id")
|
|
46
|
+
actor_json=$(json_escape "$actor")
|
|
47
|
+
action_json=$(json_escape "$action")
|
|
48
|
+
message_json=$(json_escape "$message")
|
|
49
|
+
|
|
50
|
+
printf '{"seq":%s,"at":"%s","task_id":"%s","actor":"%s","action":"%s","message":"%s"}\n' \
|
|
51
|
+
"$seq" "$at" "$task_id_json" "$actor_json" "$action_json" "$message_json" >> "$event_log"
|
|
52
|
+
|
|
53
|
+
printf 'event: %s seq:%s\n' "$event_log" "$seq"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# post-receipt: push a receipt METADATA pointer to the hosted plane.
|
|
3
|
+
#
|
|
4
|
+
# Per hosted-plane-boundary.v0 — content stays in the repo; only the
|
|
5
|
+
# pointer (task_id / seat_id / status / artifact_ref / receipt_ref /
|
|
6
|
+
# validation_summary capped) is sent. Sanitisers reject path/hostname/IP
|
|
7
|
+
# leakage in the refs.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# sh bin/post-receipt --task-id DEMO-001 --status success \
|
|
11
|
+
# --artifact-ref repo:main --receipt-ref docs/work/receipts/DEMO-001.md \
|
|
12
|
+
# --validation-summary "product-doctor: ok"
|
|
13
|
+
set -eu
|
|
14
|
+
|
|
15
|
+
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|
16
|
+
# shellcheck source=_lib/sanitize.sh
|
|
17
|
+
. "$ROOT/bin/_lib/sanitize.sh"
|
|
18
|
+
# shellcheck source=_lib/state.sh
|
|
19
|
+
. "$ROOT/bin/_lib/state.sh"
|
|
20
|
+
# shellcheck source=_lib/http.sh
|
|
21
|
+
. "$ROOT/bin/_lib/http.sh"
|
|
22
|
+
|
|
23
|
+
ALLOWED_STATUSES="success partial blocked failed"
|
|
24
|
+
|
|
25
|
+
task_id=""
|
|
26
|
+
status=""
|
|
27
|
+
artifact_ref=""
|
|
28
|
+
receipt_ref=""
|
|
29
|
+
validation_summary=""
|
|
30
|
+
|
|
31
|
+
while [ "$#" -gt 0 ]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--task-id) task_id=$2; shift 2 ;;
|
|
34
|
+
--status) status=$2; shift 2 ;;
|
|
35
|
+
--artifact-ref) artifact_ref=$2; shift 2 ;;
|
|
36
|
+
--receipt-ref) receipt_ref=$2; shift 2 ;;
|
|
37
|
+
--validation-summary) validation_summary=$2; shift 2 ;;
|
|
38
|
+
-h|--help)
|
|
39
|
+
cat <<EOF
|
|
40
|
+
post-receipt — push a receipt metadata pointer
|
|
41
|
+
|
|
42
|
+
sh bin/post-receipt --task-id <id> --status <s> \\
|
|
43
|
+
[--artifact-ref <ref>] [--receipt-ref <repo-relative-path>] \\
|
|
44
|
+
[--validation-summary <≤200 chars>]
|
|
45
|
+
|
|
46
|
+
Status (4):
|
|
47
|
+
$(printf ' - %s\n' $ALLOWED_STATUSES)
|
|
48
|
+
|
|
49
|
+
Requires seat-consume-pass to have populated workspace.json.
|
|
50
|
+
Only metadata flows over the wire; receipt body stays in your repo.
|
|
51
|
+
EOF
|
|
52
|
+
exit 0
|
|
53
|
+
;;
|
|
54
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
[ -n "$task_id" ] || { echo "post-receipt: --task-id required" >&2; exit 2; }
|
|
59
|
+
[ -n "$status" ] || { echo "post-receipt: --status required" >&2; exit 2; }
|
|
60
|
+
|
|
61
|
+
# Status allowlist.
|
|
62
|
+
ok=0
|
|
63
|
+
for s in $ALLOWED_STATUSES; do
|
|
64
|
+
if [ "$s" = "$status" ]; then ok=1; break; fi
|
|
65
|
+
done
|
|
66
|
+
if [ "$ok" -ne 1 ]; then
|
|
67
|
+
printf 'post-receipt: --status "%s" not in allowlist:\n' "$status" >&2
|
|
68
|
+
printf ' - %s\n' $ALLOWED_STATUSES >&2
|
|
69
|
+
exit 2
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# task_id length cap (defence; api caps at 128).
|
|
73
|
+
if [ "${#task_id}" -gt 128 ]; then
|
|
74
|
+
echo "post-receipt: --task-id length > 128" >&2
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Sanitise optional refs.
|
|
79
|
+
clean_artifact=""
|
|
80
|
+
if [ -n "$artifact_ref" ]; then
|
|
81
|
+
if ! clean_artifact=$(etq_sanitize_artifact_ref "$artifact_ref"); then
|
|
82
|
+
echo "post-receipt: --artifact-ref rejected" >&2
|
|
83
|
+
exit 2
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
clean_receipt=""
|
|
87
|
+
if [ -n "$receipt_ref" ]; then
|
|
88
|
+
if ! clean_receipt=$(etq_sanitize_repo_relative "$receipt_ref"); then
|
|
89
|
+
echo "post-receipt: --receipt-ref rejected" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
# Validation summary cap at 200 chars (server also enforces).
|
|
94
|
+
if [ "${#validation_summary}" -gt 200 ]; then
|
|
95
|
+
validation_summary=$(printf '%s' "$validation_summary" | cut -c1-200)
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Load workspace.json.
|
|
99
|
+
workspace_file=$(etq_state_workspace_path)
|
|
100
|
+
if [ ! -f "$workspace_file" ]; then
|
|
101
|
+
echo "post-receipt: workspace.json missing — run \`sh bin/seat-consume-pass --pass-id <id>\` first" >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
workspace_id=$(etq_state_read_key "$workspace_file" workspace_id) || exit 1
|
|
105
|
+
pass_id=$(etq_state_read_key "$workspace_file" pass_id) || exit 1
|
|
106
|
+
host=$(etq_state_read_key "$workspace_file" hosted_host) || exit 1
|
|
107
|
+
seat_id=$(etq_state_read_key "$(etq_state_seat_path)" seat_id) || exit 1
|
|
108
|
+
seat_id=$(etq_sanitize_opaque_id "$seat_id") || {
|
|
109
|
+
echo "post-receipt: seat.json contains invalid seat_id; re-run seat-init --force" >&2
|
|
110
|
+
exit 1
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Build body with jq for safe JSON construction.
|
|
114
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
115
|
+
echo "post-receipt: jq required" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
body=$(jq -nc \
|
|
119
|
+
--arg pass_id "$pass_id" \
|
|
120
|
+
--arg task_id "$task_id" \
|
|
121
|
+
--arg seat_id "$seat_id" \
|
|
122
|
+
--arg status "$status" \
|
|
123
|
+
--arg artifact_ref "$clean_artifact" \
|
|
124
|
+
--arg receipt_ref "$clean_receipt" \
|
|
125
|
+
--arg validation_summary "$validation_summary" \
|
|
126
|
+
'
|
|
127
|
+
{pass_id: $pass_id, task_id: $task_id, seat_id: $seat_id, status: $status}
|
|
128
|
+
+ (if $artifact_ref != "" then {artifact_ref: $artifact_ref} else {} end)
|
|
129
|
+
+ (if $receipt_ref != "" then {receipt_ref: $receipt_ref} else {} end)
|
|
130
|
+
+ (if $validation_summary != "" then {validation_summary: $validation_summary} else {} end)
|
|
131
|
+
')
|
|
132
|
+
|
|
133
|
+
url="$host/workspaces/$workspace_id/receipts"
|
|
134
|
+
printf 'post-receipt: POST %s task=%s status=%s\n' "$url" "$task_id" "$status"
|
|
135
|
+
|
|
136
|
+
response=$(etq_http_post "$url" "$body") || {
|
|
137
|
+
rc=$?
|
|
138
|
+
echo "post-receipt: ingest failed (http rc=$rc)" >&2
|
|
139
|
+
exit "$rc"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
printf 'post-receipt: ingested\n'
|
|
143
|
+
printf '%s\n' "$response" | jq -c . 2>/dev/null || printf '%s\n' "$response"
|