Repository navigation
fix(fm-send): restore --resolve-key flag parsing after upstream merge #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e4a08b
6b5c60d
b129e97
eb3454f
02c1513
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"hooks":{"UserPromptSubmit":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' busy --gen 'g1786408690.89755.6034' --source claude-hook --event user-prompt-submit 2>/dev/null || true"}]}],"Stop":[{"hooks":[{"type":"command","command":"touch '/Users/trilliumsmith/code/firstmate/state/fix-main-ci-fm-send.turn-ended'; '/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event stop 2>/dev/null || true"}]}],"StopFailure":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event stop-failure 2>/dev/null || true"}]}],"SessionEnd":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event session-end 2>/dev/null || true"}]}]}} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -318,6 +318,39 @@ fm_send_resolve_target "$RAW_TARGET" || exit 1 | |
| T=$RESOLVED_TARGET | ||
| shift | ||
|
|
||
| # Collect --resolve-key flags (answerer-closes; see the header contract). They | ||
| # must precede --key or the message text; everything after the last flag is the | ||
| # message exactly as before, so ordinary sends are byte-identical. | ||
| RESOLVE_KEYS= | ||
| fm_send_add_resolve_key() { # <key> | ||
| local k=$1 | ||
| case "$k" in | ||
| ''|*[!A-Za-z0-9._-]*) | ||
| echo "error: --resolve-key '$k' is not a valid decision key (allowed: A-Z a-z 0-9 . _ -)" >&2 | ||
| return 1 | ||
| ;; | ||
| esac | ||
| case " $RESOLVE_KEYS " in | ||
| *" $k "*) | ||
| echo "error: duplicate --resolve-key '$k'" >&2 | ||
| return 1 | ||
| ;; | ||
| esac | ||
| RESOLVE_KEYS="${RESOLVE_KEYS}${RESOLVE_KEYS:+ }$k" | ||
| } | ||
| while :; do | ||
| case "${1:-}" in | ||
| --resolve-key) | ||
| [ $# -ge 2 ] || { echo "error: --resolve-key requires a key" >&2; exit 1; } | ||
| fm_send_add_resolve_key "$2" || exit 1 | ||
| shift 2 | ||
|
Comment on lines
+343
to
+346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' 'Option definitions:'
rg -n -C 4 -- '--resolve-key|--key' bin/fm-send.sh || true
printf '%s\n' 'Existing coverage:'
rg -n -C 2 --glob '*test*' --glob '*.sh' \
-- '--resolve-key|duplicate|requires a key' . || trueRepository: trillium/firstmate Length of output: 50375 π Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' 'fm-send.sh header and parser:'
sed -n '1,110p' bin/fm-send.sh
sed -n '300,475p' bin/fm-send.sh
printf '%s\n' 'Resolve-key and close-flow definitions/usages:'
rg -n -C 6 'fm_send_close_resolved_keys|RESOLVE_KEYS|decision key|valid decision key' \
bin tests --glob '*.sh' --glob '*test*' | head -n 240
printf '%s\n' 'fm-send-specific tests:'
fd -i 'fm-send' tests binRepository: trillium/firstmate Length of output: 32867 π Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' 'Resolve-key tests:'
sed -n '1,430p' tests/fm-send-resolve-key.test.sh
printf '%s\n' 'Decision-key parsing and validation:'
rg -n -C 8 'status_open_decisions|key=.*A-Za-z|validate.*key|decision.*key|pending-reply-' \
bin/fm-classify-lib.sh bin/fm-pending-reply-lib.sh tests/fm-watch-triage.test.sh tests/fm-remote-reply.test.sh | head -n 320Repository: trillium/firstmate Length of output: 42345 π Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
allowed = re.compile(r'^[A-Za-z0-9._-]+$')
def parse(args):
keys = []
i = 0
while i < len(args) and args[i] == '--resolve-key':
if i + 1 >= len(args):
raise ValueError('requires a key')
key = args[i + 1]
if not allowed.fullmatch(key):
raise ValueError('invalid key')
keys.append(key)
i += 2
return keys, args[i:]
def close_lines(keys, answer):
return [f'resolved [key={key}]: answered: {answer}' for key in keys]
for case in (
['--resolve-key', '--key', 'Enter'],
['--resolve-key', '--resolve-key', 'answer'],
):
keys, rest = parse(case)
print(f'input={case!r}')
print(f'accepted_keys={keys!r}')
print(f'remaining_args={rest!r}')
print(f'close_lines={close_lines(keys, "answer")!r}')
print(f'reserved_token_accepted={any(k.startswith("--") for k in keys)}')
PYRepository: trillium/firstmate Length of output: 532 Reject reserved option tokens as
π€ Prompt for AI Agents |
||
| ;; | ||
| *) | ||
| break | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| # Raw/unmanaged pane escape hatch: best-effort literal send + Enter with no | ||
| # submit verification and no from-firstmate marking (see header). Parsed here so | ||
| # it precedes both the --key branch and the verified text path. | ||
|
|
@@ -577,7 +610,10 @@ else | |
| # Delivery is fully confirmed: close each answered decision in this home's | ||
| # ledger (answerer-closes; see the header contract). | ||
| if [ -n "$RESOLVE_KEYS" ]; then | ||
| fm_send_close_resolved_keys "$RESOLVE_ANSWER_TEXT" || exit 1 | ||
| # Extract the plain answer text (strip marker and corr token if present). | ||
| answer_text= | ||
| fm_pending_reply_carrier_body "$MESSAGE" answer_text | ||
| fm_send_close_resolved_keys "$answer_text" || exit 1 | ||
| fi | ||
| # Submit landed with exact empty. Confirmation only proves the text was | ||
| # accepted; the harness still needs a beat to spin up the | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π©Ί Stability & Availability | π Major | β‘ Quick win
Preserve failure when creating the turn-ended marker.
The Stop hook uses
touch ...; fm-busy-event.sh ... || true. Iftouchfails, the command still applies theidleevent and returns success. The downstream classifier then receives no.turn-endedmarker, so wake handling can be lost.Make marker creation a prerequisite for the lifecycle update:
Proposed fix
π Committable suggestion
π€ Prompt for AI Agents
π©Ί Stability & Availability | π Major | β‘ Quick win
π§© Analysis chain
π Script executed:
Repository: trillium/firstmate
Length of output: 5341
π Script executed:
Repository: trillium/firstmate
Length of output: 33916
π Script executed:
Repository: trillium/firstmate
Length of output: 3206
π Script executed:
Repository: trillium/firstmate
Length of output: 307
Stop tracking this generated local hook file.
.claude/settings.local.jsonis ignored and must not be committed. Its tracked contents bind hooks to one checkout, task, and generation. Remove it from the repository and retain the existing ignore rule.π€ Prompt for AI Agents